if (err)
goto out_put;
+ set_dp_devs_mtu(dp, dev);
dp_sysfs_add_if(dp->ports[port_no]);
- err = __put_user(port_no, &port.port);
+ err = __put_user(port_no, &portp->port);
out_put:
dev_put(dev);
return xstrdup(short_options);
}
- proctitle_init(int argc UNUSED, char **argv UNUSED)
+/* Runs the command designated by argv[0] within the command table specified by
+ * 'commands', which must be terminated by a command whose 'name' member is a
+ * null pointer.
+ *
+ * Command-line options should be stripped off, so that a typical invocation
+ * looks like "run_command(argc - optind, argv + optind, my_commands);". */
+void
+run_command(int argc, char *argv[], const struct command commands[])
+{
+ const struct command *p;
+
+ if (argc < 1) {
+ ovs_fatal(0, "missing command name; use --help for help");
+ }
+
+ for (p = commands; p->name != NULL; p++) {
+ if (!strcmp(p->name, argv[0])) {
+ int n_arg = argc - 1;
+ if (n_arg < p->min_args) {
+ ovs_fatal(0, "'%s' command requires at least %d arguments",
+ p->name, p->min_args);
+ } else if (n_arg > p->max_args) {
+ ovs_fatal(0, "'%s' command takes at most %d arguments",
+ p->name, p->max_args);
+ } else {
+ p->handler(argc, argv);
+ if (ferror(stdout)) {
+ ovs_fatal(0, "write to stdout failed");
+ }
+ if (ferror(stderr)) {
+ ovs_fatal(0, "write to stderr failed");
+ }
+ return;
+ }
+ }
+ }
+
+ ovs_fatal(0, "unknown command '%s'; use --help for help", argv[0]);
+}
+\f
+/* Process title. */
+
+#ifdef __linux__
+static char *argv_start; /* Start of command-line arguments in memory. */
+static size_t argv_size; /* Number of bytes of command-line arguments. */
+static char *saved_proctitle; /* Saved command-line arguments. */
+
+/* Prepares the process so that proctitle_set() can later succeed.
+ *
+ * This modifies the argv[] array so that it no longer points into the memory
+ * that it originally does. Later, proctitle_set() might overwrite that
+ * memory. That means that this function should be called before anything else
+ * that accesses the process's argv[] array. Ideally, it should be called
+ * before anything else, period, at the very beginning of program
+ * execution. */
+void
+proctitle_init(int argc, char **argv)
+{
+ int i;
+
+ if (!argc || !argv[0]) {
+ /* This situation should never occur, but... */
+ return;
+ }
+
+ /* Specialized version of first loop iteration below. */
+ argv_start = argv[0];
+ argv_size = strlen(argv[0]) + 1;
+ argv[0] = xstrdup(argv[0]);
+
+ for (i = 1; i < argc; i++) {
+ size_t size = strlen(argv[i]) + 1;
+
+ /* Add (argv[i], strlen(argv[i])+1) to (argv_start, argv_size). */
+ if (argv[i] + size == argv_start) {
+ /* Arguments grow downward in memory. */
+ argv_start -= size;
+ argv_size += size;
+ } else if (argv[i] == argv_start + argv_size) {
+ /* Arguments grow upward in memory. */
+ argv_size += size;
+ } else {
+ /* Arguments not contiguous. (Is this really Linux?) */
+ }
+
+ /* Copy out the old argument so we can reuse the space. */
+ argv[i] = xstrdup(argv[i]);
+ }
+}
+
+/* Changes the name of the process, as shown by "ps", to 'format', which is
+ * formatted as if by printf(). */
+void
+proctitle_set(const char *format, ...)
+{
+ va_list args;
+ int n;
+
+ if (!argv_start || argv_size < 8) {
+ return;
+ }
+
+ if (!saved_proctitle) {
+ saved_proctitle = xmemdup(argv_start, argv_size);
+ }
+
+ va_start(args, format);
+ n = vsnprintf(argv_start, argv_size, format, args);
+ if (n >= argv_size) {
+ /* The name is too long, so add an ellipsis at the end. */
+ strcpy(&argv_start[argv_size - 4], "...");
+ } else {
+ /* Fill the extra space with null bytes, so that trailing bytes don't
+ * show up in the command line. */
+ memset(&argv_start[n], '\0', argv_size - n);
+ }
+ va_end(args);
+}
+
+/* Restores the process's original command line, as seen by "ps". */
+void
+proctitle_restore(void)
+{
+ if (saved_proctitle) {
+ memcpy(argv_start, saved_proctitle, argv_size);
+ free(saved_proctitle);
+ saved_proctitle = NULL;
+ }
+}
+#else /* !__linux__ */
+/* Stubs that don't do anything on non-Linux systems. */
+
+void
- proctitle_set(const char *format UNUSED, ...)
++proctitle_init(int argc OVS_UNUSED, char **argv OVS_UNUSED)
+{
+}
+
+void
++proctitle_set(const char *format OVS_UNUSED, ...)
+{
+}
+
+void
+proctitle_restore(void)
+{
+}
+#endif /* !__linux__ */
static unsigned int epoch;
static void
- coverage_unixctl_log(struct unixctl_conn *conn, const char *args UNUSED,
- void *aux UNUSED)
-coverage_unixctl_log(struct unixctl_conn *conn, const char *args OVS_UNUSED)
++coverage_unixctl_log(struct unixctl_conn *conn, const char *args OVS_UNUSED,
++ void *aux OVS_UNUSED)
{
coverage_log(VLL_WARN, false);
unixctl_command_reply(conn, 200, NULL);
unsigned int word_mask = 1u << (bit_index % BITS_PER_WORD);
if (hit[word_index] & word_mask) {
-- return true;
++ return true;
} else {
hit[word_index] |= word_mask;
return false;
}
static int
- dpif_linux_open(const char *name, const char *type UNUSED, bool create,
-dpif_linux_open(const char *name OVS_UNUSED, char *suffix, bool create,
++dpif_linux_open(const char *name, const char *type OVS_UNUSED, bool create,
struct dpif **dpifp)
{
int minor;
}
static int
- dpif_netdev_open(const char *name, const char *type UNUSED, bool create,
-dpif_netdev_open(const char *name OVS_UNUSED, char *suffix, bool create,
++dpif_netdev_open(const char *name, const char *type OVS_UNUSED, bool create,
struct dpif **dpifp)
{
if (create) {
do_unlink_files();
}
-/* This is a fatal_signal_add_hook() callback (via unlink_files()). It will be
- * invoked from an asynchronous signal handler, so it cannot call most C
- * library functions (unlink() is an explicit exception, see
- * http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html).
- * That includes free(), so it doesn't try to free the 'files' data
- * structure. */
+static void
- cancel_files(void *aux UNUSED)
++cancel_files(void *aux OVS_UNUSED)
+{
+ shash_clear(&files);
+ added_hook = false;
+}
+
static void
do_unlink_files(void)
{
static void
netdev_linux_cache_cb(const struct rtnetlink_change *change,
- void *aux UNUSED)
+ void *aux OVS_UNUSED)
{
- struct netdev_linux_cache *cache;
+ struct netdev_dev_linux *dev;
if (change) {
- cache = shash_find_data(&cache_map, change->ifname);
- if (cache) {
- cache->valid = 0;
+ struct netdev_dev *base_dev = netdev_dev_from_name(change->ifname);
+ if (base_dev) {
+ dev = netdev_dev_linux_cast(base_dev);
+ dev->cache_valid = 0;
}
} else {
+ struct shash device_shash;
struct shash_node *node;
- SHASH_FOR_EACH (node, &cache_map) {
- cache = node->data;
- cache->valid = 0;
+
+ shash_init(&device_shash);
+ netdev_dev_get_devices(&netdev_linux_class, &device_shash);
+ SHASH_FOR_EACH (node, &device_shash) {
+ dev = node->data;
+ dev->cache_valid = 0;
}
+ shash_destroy(&device_shash);
}
}
-/* Creates the netdev object of 'type' with 'name'. */
+/* The arguments are marked as unused to prevent warnings on platforms where
+ * the Netlink interface isn't supported. */
static int
- setup_gre_netlink(const char *name UNUSED, struct gre_config *config UNUSED,
- bool create UNUSED)
-netdev_linux_create(const char *name, const char *type,
- const struct shash *args, bool created)
++setup_gre_netlink(const char *name OVS_UNUSED,
++ struct gre_config *config OVS_UNUSED, bool create OVS_UNUSED)
{
- struct netdev_obj_linux *netdev_obj;
- static const char tap_dev[] = "/dev/net/tun";
+#ifdef GRE_IOCTL_ONLY
+ return EOPNOTSUPP;
+#else
+ int error;
+ struct ofpbuf request, *reply;
+ unsigned int nl_flags;
+ struct ifinfomsg ifinfomsg;
+ struct nlattr *linkinfo_hdr;
+ struct nlattr *info_data_hdr;
+ uint16_t iflags = 0;
+ uint16_t oflags = 0;
+ uint8_t pmtudisc = 0;
+
+ VLOG_DBG("%s: attempting to create gre device using netlink", name);
+
+ if (!gre_descriptors.nl_sock) {
+ error = nl_sock_create(NETLINK_ROUTE, 0, 0, 0,
+ &gre_descriptors.nl_sock);
+ if (error) {
+ VLOG_WARN("couldn't create netlink socket: %s", strerror(error));
+ goto error;
+ }
+ }
+
+ ofpbuf_init(&request, 0);
+
+ nl_flags = NLM_F_REQUEST;
+ if (create) {
+ nl_flags |= NLM_F_CREATE|NLM_F_EXCL;
+ }
+
+ /* We over-reserve space, because we do some pointer arithmetic
+ * and don't want the buffer address shifting under us. */
+ nl_msg_put_nlmsghdr(&request, gre_descriptors.nl_sock, 2048, RTM_NEWLINK,
+ nl_flags);
+
+ memset(&ifinfomsg, 0, sizeof ifinfomsg);
+ ifinfomsg.ifi_family = AF_UNSPEC;
+ nl_msg_put(&request, &ifinfomsg, sizeof ifinfomsg);
+
+ linkinfo_hdr = ofpbuf_tail(&request);
+ nl_msg_put_unspec(&request, IFLA_LINKINFO, NULL, 0);
+
+ nl_msg_put_unspec(&request, IFLA_INFO_KIND, "gretap", 6);
+
+ info_data_hdr = ofpbuf_tail(&request);
+ nl_msg_put_unspec(&request, IFLA_INFO_DATA, NULL, 0);
+
+ /* Set flags */
+ if (config->have_in_key) {
+ iflags |= GRE_KEY;
+ }
+ if (config->have_out_key) {
+ oflags |= GRE_KEY;
+ }
+
+ if (config->in_csum) {
+ iflags |= GRE_CSUM;
+ }
+ if (config->out_csum) {
+ oflags |= GRE_CSUM;
+ }
+
+ /* Add options */
+ nl_msg_put_u32(&request, IFLA_GRE_IKEY, config->in_key);
+ nl_msg_put_u32(&request, IFLA_GRE_OKEY, config->out_key);
+ nl_msg_put_u16(&request, IFLA_GRE_IFLAGS, iflags);
+ nl_msg_put_u16(&request, IFLA_GRE_OFLAGS, oflags);
+ nl_msg_put_u32(&request, IFLA_GRE_LOCAL, config->local_ip);
+ nl_msg_put_u32(&request, IFLA_GRE_REMOTE, config->remote_ip);
+ nl_msg_put_u8(&request, IFLA_GRE_PMTUDISC, pmtudisc);
+ nl_msg_put_u8(&request, IFLA_GRE_TTL, 0);
+ nl_msg_put_u8(&request, IFLA_GRE_TOS, 0);
+
+ info_data_hdr->nla_len = (char *)ofpbuf_tail(&request)
+ - (char *)info_data_hdr;
+ linkinfo_hdr->nla_len = (char *)ofpbuf_tail(&request)
+ - (char *)linkinfo_hdr;
+
+ nl_msg_put_string(&request, IFLA_IFNAME, name);
+
+ error = nl_sock_transact(gre_descriptors.nl_sock, &request, &reply);
+ ofpbuf_uninit(&request);
+ if (error) {
+ VLOG_WARN("couldn't transact netlink socket: %s", strerror(error));
+ goto error;
+ }
+ ofpbuf_delete(reply);
+
+error:
+ return error;
+#endif
+}
+
+static int
+setup_gre_ioctl(const char *name, struct gre_config *config, bool create)
+{
+ struct ip_tunnel_parm p;
struct ifreq ifr;
- check_gre_device_netlink(const char *name UNUSED)
+
+ VLOG_DBG("%s: attempting to create gre device using ioctl", name);
+
+ memset(&p, 0, sizeof p);
+
+ strncpy(p.name, name, IFNAMSIZ);
+
+ p.iph.version = 4;
+ p.iph.ihl = 5;
+ p.iph.protocol = IPPROTO_GRE;
+ p.iph.saddr = config->local_ip;
+ p.iph.daddr = config->remote_ip;
+
+ if (config->have_in_key) {
+ p.i_flags |= GRE_KEY;
+ p.i_key = config->in_key;
+ }
+ if (config->have_out_key) {
+ p.o_flags |= GRE_KEY;
+ p.o_key = config->out_key;
+ }
+
+ if (config->in_csum) {
+ p.i_flags |= GRE_CSUM;
+ }
+ if (config->out_csum) {
+ p.o_flags |= GRE_CSUM;
+ }
+
+ strncpy(ifr.ifr_name, create ? GRE_IOCTL_DEVICE : name, IFNAMSIZ);
+ ifr.ifr_ifru.ifru_data = (void *)&p;
+
+ if (!gre_descriptors.ioctl_fd) {
+ gre_descriptors.ioctl_fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (gre_descriptors.ioctl_fd < 0) {
+ VLOG_WARN("couldn't create gre ioctl socket: %s", strerror(errno));
+ gre_descriptors.ioctl_fd = 0;
+ return errno;
+ }
+ }
+
+ if (ioctl(gre_descriptors.ioctl_fd, create ? SIOCADDGRETAP : SIOCCHGGRETAP,
+ &ifr) < 0) {
+ VLOG_WARN("couldn't do gre ioctl: %s", strerror(errno));
+ return errno;
+ }
+
+ return 0;
+}
+
+/* The arguments are marked as unused to prevent warnings on platforms where
+ * the Netlink interface isn't supported. */
+static bool
- netdev_linux_create_system(const char *name, const char *type UNUSED,
++check_gre_device_netlink(const char *name OVS_UNUSED)
+{
+#ifdef GRE_IOCTL_ONLY
+ return false;
+#else
+ static const struct nl_policy getlink_policy[] = {
+ [IFLA_LINKINFO] = { .type = NL_A_NESTED, .optional = false },
+ };
+
+ static const struct nl_policy linkinfo_policy[] = {
+ [IFLA_INFO_KIND] = { .type = NL_A_STRING, .optional = false },
+ };
+
+ int error;
+ bool ret = false;
+ struct ofpbuf request, *reply;
+ struct ifinfomsg ifinfomsg;
+ struct nlattr *getlink_attrs[ARRAY_SIZE(getlink_policy)];
+ struct nlattr *linkinfo_attrs[ARRAY_SIZE(linkinfo_policy)];
+ struct ofpbuf linkinfo;
+ const char *device_kind;
+
+ ofpbuf_init(&request, 0);
+
+ nl_msg_put_nlmsghdr(&request, gre_descriptors.nl_sock,
+ NLMSG_LENGTH(sizeof ifinfomsg), RTM_GETLINK,
+ NLM_F_REQUEST);
+
+ memset(&ifinfomsg, 0, sizeof ifinfomsg);
+ ifinfomsg.ifi_family = AF_UNSPEC;
+ ifinfomsg.ifi_index = do_get_ifindex(name);
+ nl_msg_put(&request, &ifinfomsg, sizeof ifinfomsg);
+
+ error = nl_sock_transact(gre_descriptors.nl_sock, &request, &reply);
+ ofpbuf_uninit(&request);
+ if (error) {
+ VLOG_WARN("couldn't transact netlink socket: %s", strerror(error));
+ return false;
+ }
+
+ if (!nl_policy_parse(reply, NLMSG_HDRLEN + sizeof(struct ifinfomsg),
+ getlink_policy, getlink_attrs,
+ ARRAY_SIZE(getlink_policy))) {
+ VLOG_WARN("received bad rtnl message (getlink policy)");
+ goto error;
+ }
+
+ linkinfo.data = (void *)nl_attr_get(getlink_attrs[IFLA_LINKINFO]);
+ linkinfo.size = nl_attr_get_size(getlink_attrs[IFLA_LINKINFO]);
+ if (!nl_policy_parse(&linkinfo, 0, linkinfo_policy,
+ linkinfo_attrs, ARRAY_SIZE(linkinfo_policy))) {
+ VLOG_WARN("received bad rtnl message (linkinfo policy)");
+ goto error;
+ }
+
+ device_kind = nl_attr_get_string(linkinfo_attrs[IFLA_INFO_KIND]);
+ ret = !strcmp(device_kind, "gretap");
+
+error:
+ ofpbuf_delete(reply);
+ return ret;
+#endif
+}
+
+static bool
+check_gre_device_ioctl(const char *name)
+{
+ struct ethtool_drvinfo drvinfo;
+ int error;
+
+ memset(&drvinfo, 0, sizeof drvinfo);
+ error = netdev_linux_do_ethtool(name, (struct ethtool_cmd *)&drvinfo,
+ ETHTOOL_GDRVINFO, "ETHTOOL_GDRVINFO");
+
+ return !error && !strcmp(drvinfo.driver, "ip_gre")
+ && !strcmp(drvinfo.bus_info, "gretap");
+}
+
+static int
+setup_gre(const char *name, const struct shash *args, bool create)
+{
+ int error;
+ struct in_addr in_addr;
+ struct shash_node *node;
+ struct gre_config config;
+
+ memset(&config, 0, sizeof config);
+ config.in_csum = true;
+ config.out_csum = true;
+
+ SHASH_FOR_EACH (node, args) {
+ if (!strcmp(node->name, "remote_ip")) {
+ if (lookup_ip(node->data, &in_addr)) {
+ VLOG_WARN("bad 'remote_ip' for gre device %s ", name);
+ } else {
+ config.remote_ip = in_addr.s_addr;
+ }
+ } else if (!strcmp(node->name, "local_ip")) {
+ if (lookup_ip(node->data, &in_addr)) {
+ VLOG_WARN("bad 'local_ip' for gre device %s ", name);
+ } else {
+ config.local_ip = in_addr.s_addr;
+ }
+ } else if (!strcmp(node->name, "key")) {
+ config.have_in_key = true;
+ config.have_out_key = true;
+ config.in_key = htonl(atoi(node->data));
+ config.out_key = htonl(atoi(node->data));
+ } else if (!strcmp(node->name, "in_key")) {
+ config.have_in_key = true;
+ config.in_key = htonl(atoi(node->data));
+ } else if (!strcmp(node->name, "out_key")) {
+ config.have_out_key = true;
+ config.out_key = htonl(atoi(node->data));
+ } else if (!strcmp(node->name, "csum")) {
+ if (!strcmp(node->data, "false")) {
+ config.in_csum = false;
+ config.out_csum = false;
+ }
+ } else {
+ VLOG_WARN("unknown gre argument '%s'", node->name);
+ }
+ }
+
+ if (!config.remote_ip) {
+ VLOG_WARN("gre type requires valid 'remote_ip' argument");
+ error = EINVAL;
+ goto error;
+ }
+
+ if (!gre_descriptors.use_ioctl) {
+ error = setup_gre_netlink(name, &config, create);
+ if (error == EOPNOTSUPP) {
+ gre_descriptors.use_ioctl = true;
+ }
+ }
+ if (gre_descriptors.use_ioctl) {
+ error = setup_gre_ioctl(name, &config, create);
+ }
+
+ if (create && error == EEXIST) {
+ bool gre_device;
+
+ if (gre_descriptors.use_ioctl) {
+ gre_device = check_gre_device_ioctl(name);
+ } else {
+ gre_device = check_gre_device_netlink(name);
+ }
+
+ if (!gre_device) {
+ goto error;
+ }
+
+ VLOG_WARN("replacing existing gre device %s", name);
+ error = destroy_gre(name);
+ if (error) {
+ goto error;
+ }
+
+ if (gre_descriptors.use_ioctl) {
+ error = setup_gre_ioctl(name, &config, create);
+ } else {
+ error = setup_gre_netlink(name, &config, create);
+ }
+ }
+
+error:
+ return error;
+}
+
+/* Creates the netdev device of 'type' with 'name'. */
+static int
++netdev_linux_create_system(const char *name, const char *type OVS_UNUSED,
+ const struct shash *args, struct netdev_dev **netdev_devp)
+{
+ struct netdev_dev_linux *netdev_dev;
int error;
if (!shash_is_empty(args)) {
- VLOG_WARN("arguments for %s devices should be empty", type);
+ VLOG_WARN("%s: arguments for system devices should be empty", name);
}
- /* Create the name binding in the netdev library for this object. */
- netdev_obj = xcalloc(1, sizeof *netdev_obj);
- netdev_obj_init(&netdev_obj->netdev_obj, name, &netdev_linux_class,
- created);
- netdev_obj->tap_fd = -1;
+ if (!cache_notifier_refcount) {
+ error = rtnetlink_notifier_register(&netdev_linux_cache_notifier,
+ netdev_linux_cache_cb, NULL);
+ if (error) {
+ return error;
+ }
+ }
+ cache_notifier_refcount++;
- if (strcmp(type, "tap")) {
- return 0;
+ netdev_dev = xzalloc(sizeof *netdev_dev);
+ netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_linux_class);
+
+ *netdev_devp = &netdev_dev->netdev_dev;
+ return 0;
+}
+
+/* For most types of netdevs we open the device for each call of
+ * netdev_open(). However, this is not the case with tap devices,
+ * since it is only possible to open the device once. In this
+ * situation we share a single file descriptor, and consequently
+ * buffers, across all readers. Therefore once data is read it will
+ * be unavailable to other reads for tap devices. */
+static int
- netdev_linux_create_tap(const char *name, const char *type UNUSED,
++netdev_linux_create_tap(const char *name, const char *type OVS_UNUSED,
+ const struct shash *args, struct netdev_dev **netdev_devp)
+{
+ struct netdev_dev_linux *netdev_dev;
+ struct tap_state *state;
+ static const char tap_dev[] = "/dev/net/tun";
+ struct ifreq ifr;
+ int error;
+
+ if (!shash_is_empty(args)) {
+ VLOG_WARN("%s: arguments for TAP devices should be empty", name);
}
+ netdev_dev = xzalloc(sizeof *netdev_dev);
+ state = &netdev_dev->state.tap;
+
/* Open tap device. */
- netdev_obj->tap_fd = open(tap_dev, O_RDWR);
- if (netdev_obj->tap_fd < 0) {
+ state->fd = open(tap_dev, O_RDWR);
+ if (state->fd < 0) {
error = errno;
VLOG_WARN("opening \"%s\" failed: %s", tap_dev, strerror(error));
goto error;
}
static int
- netdev_linux_create_gre(const char *name, const char *type UNUSED,
-netdev_linux_open(const char *name, int ethertype, struct netdev **netdevp)
++netdev_linux_create_gre(const char *name, const char *type OVS_UNUSED,
+ const struct shash *args, struct netdev_dev **netdev_devp)
{
- struct netdev_linux *netdev;
- enum netdev_flags flags;
+ struct netdev_dev_linux *netdev_dev;
int error;
- /* Allocate network device. */
- netdev = xcalloc(1, sizeof *netdev);
- netdev_init(&netdev->netdev, name, &netdev_linux_class);
- netdev->netdev_fd = -1;
- netdev->tap_fd = -1;
- netdev->cache = shash_find_data(&cache_map, name);
- if (!netdev->cache) {
- if (shash_is_empty(&cache_map)) {
- int error = rtnetlink_notifier_register(
- &netdev_linux_cache_notifier, netdev_linux_cache_cb, NULL);
- if (error) {
- netdev_close(&netdev->netdev);
- return error;
- }
- }
- netdev->cache = xmalloc(sizeof *netdev->cache);
- netdev->cache->shash_node = shash_add(&cache_map, name,
- netdev->cache);
- netdev->cache->valid = 0;
- netdev->cache->ref_cnt = 0;
+ netdev_dev = xzalloc(sizeof *netdev_dev);
+
+ error = setup_gre(name, args, true);
+ if (error) {
+ goto error;
}
- netdev->cache->ref_cnt++;
- if (!strcmp(netdev_get_type(&netdev->netdev), "tap")) {
- static const char tap_dev[] = "/dev/net/tun";
- struct ifreq ifr;
+ error = if_up(name);
+ if (error) {
+ goto error;
+ }
- /* Open tap device. */
- netdev->tap_fd = open(tap_dev, O_RDWR);
- if (netdev->tap_fd < 0) {
- error = errno;
- VLOG_WARN("opening \"%s\" failed: %s", tap_dev, strerror(error));
- goto error;
- }
+ netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_gre_class);
+ *netdev_devp = &netdev_dev->netdev_dev;
+ return 0;
- /* Create tap device. */
- ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
- strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
- if (ioctl(netdev->tap_fd, TUNSETIFF, &ifr) == -1) {
- VLOG_WARN("%s: creating tap device failed: %s", name,
- strerror(errno));
- error = errno;
- goto error;
- }
+error:
+ free(netdev_dev);
+ return error;
+}
- /* Make non-blocking. */
- error = set_nonblocking(netdev->tap_fd);
- if (error) {
- goto error;
+static int
+netdev_linux_reconfigure_gre(struct netdev_dev *netdev_dev_,
+ const struct shash *args)
+{
+ const char *name = netdev_dev_get_name(netdev_dev_);
+
+ return setup_gre(name, args, false);
+}
+
+/* The arguments are marked as unused to prevent warnings on platforms where
+ * the Netlink interface isn't supported. */
+static int
- destroy_gre_netlink(const char *name UNUSED)
++destroy_gre_netlink(const char *name OVS_UNUSED)
+{
+#ifdef GRE_IOCTL_ONLY
+ return EOPNOTSUPP;
+#else
+ int error;
+ struct ofpbuf request, *reply;
+ struct ifinfomsg ifinfomsg;
+ int ifindex;
+
+ ofpbuf_init(&request, 0);
+
+ nl_msg_put_nlmsghdr(&request, gre_descriptors.nl_sock, 0, RTM_DELLINK,
+ NLM_F_REQUEST);
+
+ memset(&ifinfomsg, 0, sizeof ifinfomsg);
+ ifinfomsg.ifi_family = AF_UNSPEC;
+ nl_msg_put(&request, &ifinfomsg, sizeof ifinfomsg);
+
+ ifindex = do_get_ifindex(name);
+ nl_msg_put_u32(&request, IFLA_LINK, ifindex);
+
+ nl_msg_put_string(&request, IFLA_IFNAME, name);
+
+ error = nl_sock_transact(gre_descriptors.nl_sock, &request, &reply);
+ ofpbuf_uninit(&request);
+ if (error) {
+ VLOG_WARN("couldn't transact netlink socket: %s", strerror(error));
+ goto error;
+ }
+ ofpbuf_delete(reply);
+
+error:
+ return 0;
+#endif
+}
+
+static int
+destroy_gre_ioctl(const char *name)
+{
+ struct ip_tunnel_parm p;
+ struct ifreq ifr;
+
+ memset(&p, 0, sizeof p);
+ strncpy(p.name, name, IFNAMSIZ);
+
+ strncpy(ifr.ifr_name, name, IFNAMSIZ);
+ ifr.ifr_ifru.ifru_data = (void *)&p;
+
+ if (ioctl(gre_descriptors.ioctl_fd, SIOCDELGRETAP, &ifr) < 0) {
+ VLOG_WARN("couldn't do gre ioctl: %s\n", strerror(errno));
+ return errno;
+ }
+
+ return 0;
+}
+
+static void
+destroy_tap(struct netdev_dev_linux *netdev_dev)
+{
+ struct tap_state *state = &netdev_dev->state.tap;
+
+ if (state->fd >= 0) {
+ close(state->fd);
+ }
+}
+
+static int
+destroy_gre(const char *name)
+{
+ if (gre_descriptors.use_ioctl) {
+ return destroy_gre_ioctl(name);
+ } else {
+ return destroy_gre_netlink(name);
+ }
+}
+
+/* Destroys the netdev device 'netdev_dev_'. */
+static void
+netdev_linux_destroy(struct netdev_dev *netdev_dev_)
+{
+ struct netdev_dev_linux *netdev_dev = netdev_dev_linux_cast(netdev_dev_);
+ const char *type = netdev_dev_get_type(netdev_dev_);
+
+ if (!strcmp(type, "system")) {
+ cache_notifier_refcount--;
+
+ if (!cache_notifier_refcount) {
+ rtnetlink_notifier_unregister(&netdev_linux_cache_notifier);
}
+ } else if (!strcmp(type, "tap")) {
+ destroy_tap(netdev_dev);
+ } else if (!strcmp(type, "gre")) {
+ destroy_gre(netdev_dev_get_name(&netdev_dev->netdev_dev));
}
+ free(netdev_dev_);
+}
+
+static int
+netdev_linux_open(struct netdev_dev *netdev_dev_, int ethertype,
+ struct netdev **netdevp)
+{
+ struct netdev_dev_linux *netdev_dev = netdev_dev_linux_cast(netdev_dev_);
+ struct netdev_linux *netdev;
+ enum netdev_flags flags;
+ int error;
+
+ /* Allocate network device. */
+ netdev = xzalloc(sizeof *netdev);
+ netdev->fd = -1;
+ netdev_init(&netdev->netdev, netdev_dev_);
+
error = netdev_get_flags(&netdev->netdev, &flags);
if (error == ENODEV) {
goto error;
* additional log messages. */
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
- static void close_all_netdevs(void *aux UNUSED);
-static void restore_all_flags(void *aux);
++static void close_all_netdevs(void *aux OVS_UNUSED);
static int restore_flags(struct netdev *netdev);
+void update_device_args(struct netdev_dev *, const struct shash *args);
-/* Attempts to initialize the netdev module. Returns 0 if successful,
- * otherwise a positive errno value.
- *
- * Calling this function is optional. If not called explicitly, it will
- * automatically be called upon the first attempt to open or create a
- * network device. */
-int
+static void
netdev_initialize(void)
{
static int status = -1;
return 0;
}
-/* Retores all the flags on all network devices that we modified. Called from
- * a signal handler, so it does not attempt to report error conditions. */
+/* Close all netdevs on shutdown so they can do any needed cleanup such as
+ * destroying devices, restoring flags, etc. */
static void
- close_all_netdevs(void *aux UNUSED)
-restore_all_flags(void *aux OVS_UNUSED)
++close_all_netdevs(void *aux OVS_UNUSED)
{
- struct netdev *netdev;
- LIST_FOR_EACH (netdev, struct netdev, node, &netdev_list) {
- restore_flags(netdev);
+ struct netdev *netdev, *next;
+ LIST_FOR_EACH_SAFE(netdev, next, struct netdev, node, &netdev_list) {
+ netdev_close(netdev);
}
}
struct ofpbuf;
+bool dpid_from_string(const char *s, uint64_t *dpidp);
+
#define ETH_ADDR_LEN 6
- static const uint8_t eth_addr_broadcast[ETH_ADDR_LEN] UNUSED
+ static const uint8_t eth_addr_broadcast[ETH_ADDR_LEN] OVS_UNUSED
= { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
static inline bool eth_addr_is_broadcast(const uint8_t ea[6])
void shash_delete(struct shash *, struct shash_node *);
struct shash_node *shash_find(const struct shash *, const char *);
void *shash_find_data(const struct shash *, const char *);
+void *shash_find_and_delete(struct shash *, const char *);
struct shash_node *shash_first(const struct shash *);
+const struct shash_node **shash_sort(const struct shash *);
+bool shash_equal_keys(const struct shash *, const struct shash *);
+ #ifdef __cplusplus
+ }
+ #endif
+
#endif /* shash.h */
--- /dev/null
- static DH *tmp_dh_callback(SSL *ssl, int is_export UNUSED, int keylength);
+/*
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "stream-ssl.h"
+#include "dhparams.h"
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <string.h>
+#include <netinet/tcp.h>
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
+#include <poll.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "dynamic-string.h"
+#include "leak-checker.h"
+#include "ofpbuf.h"
+#include "openflow/openflow.h"
+#include "packets.h"
+#include "poll-loop.h"
+#include "socket-util.h"
+#include "socket-util.h"
+#include "util.h"
+#include "stream-provider.h"
+#include "stream.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_stream_ssl
+
+/* Active SSL. */
+
+enum ssl_state {
+ STATE_TCP_CONNECTING,
+ STATE_SSL_CONNECTING
+};
+
+enum session_type {
+ CLIENT,
+ SERVER
+};
+
+struct ssl_stream
+{
+ struct stream stream;
+ enum ssl_state state;
+ int connect_error;
+ enum session_type type;
+ int fd;
+ SSL *ssl;
+ struct ofpbuf *txbuf;
+
+ /* rx_want and tx_want record the result of the last call to SSL_read()
+ * and SSL_write(), respectively:
+ *
+ * - If the call reported that data needed to be read from the file
+ * descriptor, the corresponding member is set to SSL_READING.
+ *
+ * - If the call reported that data needed to be written to the file
+ * descriptor, the corresponding member is set to SSL_WRITING.
+ *
+ * - Otherwise, the member is set to SSL_NOTHING, indicating that the
+ * call completed successfully (or with an error) and that there is no
+ * need to block.
+ *
+ * These are needed because there is no way to ask OpenSSL what a data read
+ * or write would require without giving it a buffer to receive into or
+ * data to send, respectively. (Note that the SSL_want() status is
+ * overwritten by each SSL_read() or SSL_write() call, so we can't rely on
+ * its value.)
+ *
+ * A single call to SSL_read() or SSL_write() can perform both reading
+ * and writing and thus invalidate not one of these values but actually
+ * both. Consider this situation, for example:
+ *
+ * - SSL_write() blocks on a read, so tx_want gets SSL_READING.
+ *
+ * - SSL_read() laters succeeds reading from 'fd' and clears out the
+ * whole receive buffer, so rx_want gets SSL_READING.
+ *
+ * - Client calls stream_wait(STREAM_RECV) and stream_wait(STREAM_SEND)
+ * and blocks.
+ *
+ * - Now we're stuck blocking until the peer sends us data, even though
+ * SSL_write() could now succeed, which could easily be a deadlock
+ * condition.
+ *
+ * On the other hand, we can't reset both tx_want and rx_want on every call
+ * to SSL_read() or SSL_write(), because that would produce livelock,
+ * e.g. in this situation:
+ *
+ * - SSL_write() blocks, so tx_want gets SSL_READING or SSL_WRITING.
+ *
+ * - SSL_read() blocks, so rx_want gets SSL_READING or SSL_WRITING,
+ * but tx_want gets reset to SSL_NOTHING.
+ *
+ * - Client calls stream_wait(STREAM_RECV) and stream_wait(STREAM_SEND)
+ * and blocks.
+ *
+ * - Client wakes up immediately since SSL_NOTHING in tx_want indicates
+ * that no blocking is necessary.
+ *
+ * The solution we adopt here is to set tx_want to SSL_NOTHING after
+ * calling SSL_read() only if the SSL state of the connection changed,
+ * which indicates that an SSL-level renegotiation made some progress, and
+ * similarly for rx_want and SSL_write(). This prevents both the
+ * deadlock and livelock situations above.
+ */
+ int rx_want, tx_want;
+};
+
+/* SSL context created by ssl_init(). */
+static SSL_CTX *ctx;
+
+/* Required configuration. */
+static bool has_private_key, has_certificate, has_ca_cert;
+
+/* Ordinarily, we require a CA certificate for the peer to be locally
+ * available. 'has_ca_cert' is true when this is the case, and neither of the
+ * following variables matter.
+ *
+ * We can, however, bootstrap the CA certificate from the peer at the beginning
+ * of our first connection then use that certificate on all subsequent
+ * connections, saving it to a file for use in future runs also. In this case,
+ * 'has_ca_cert' is false, 'bootstrap_ca_cert' is true, and 'ca_cert_file'
+ * names the file to be saved. */
+static bool bootstrap_ca_cert;
+static char *ca_cert_file;
+
+/* Who knows what can trigger various SSL errors, so let's throttle them down
+ * quite a bit. */
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 25);
+
+static int ssl_init(void);
+static int do_ssl_init(void);
+static bool ssl_wants_io(int ssl_error);
+static void ssl_close(struct stream *);
+static void ssl_clear_txbuf(struct ssl_stream *);
+static int interpret_ssl_error(const char *function, int ret, int error,
+ int *want);
- pssl_open(const char *name UNUSED, char *suffix, struct pstream **pstreamp)
++static DH *tmp_dh_callback(SSL *ssl, int is_export OVS_UNUSED, int keylength);
+static void log_ca_cert(const char *file_name, X509 *cert);
+
+static short int
+want_to_poll_events(int want)
+{
+ switch (want) {
+ case SSL_NOTHING:
+ NOT_REACHED();
+
+ case SSL_READING:
+ return POLLIN;
+
+ case SSL_WRITING:
+ return POLLOUT;
+
+ default:
+ NOT_REACHED();
+ }
+}
+
+static int
+new_ssl_stream(const char *name, int fd, enum session_type type,
+ enum ssl_state state, const struct sockaddr_in *remote,
+ struct stream **streamp)
+{
+ struct sockaddr_in local;
+ socklen_t local_len = sizeof local;
+ struct ssl_stream *sslv;
+ SSL *ssl = NULL;
+ int on = 1;
+ int retval;
+
+ /* Check for all the needful configuration. */
+ retval = 0;
+ if (!has_private_key) {
+ VLOG_ERR("Private key must be configured to use SSL");
+ retval = ENOPROTOOPT;
+ }
+ if (!has_certificate) {
+ VLOG_ERR("Certificate must be configured to use SSL");
+ retval = ENOPROTOOPT;
+ }
+ if (!has_ca_cert && !bootstrap_ca_cert) {
+ VLOG_ERR("CA certificate must be configured to use SSL");
+ retval = ENOPROTOOPT;
+ }
+ if (!SSL_CTX_check_private_key(ctx)) {
+ VLOG_ERR("Private key does not match certificate public key: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ retval = ENOPROTOOPT;
+ }
+ if (retval) {
+ goto error;
+ }
+
+ /* Get the local IP and port information */
+ retval = getsockname(fd, (struct sockaddr *) &local, &local_len);
+ if (retval) {
+ memset(&local, 0, sizeof local);
+ }
+
+ /* Disable Nagle. */
+ retval = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof on);
+ if (retval) {
+ VLOG_ERR("%s: setsockopt(TCP_NODELAY): %s", name, strerror(errno));
+ retval = errno;
+ goto error;
+ }
+
+ /* Create and configure OpenSSL stream. */
+ ssl = SSL_new(ctx);
+ if (ssl == NULL) {
+ VLOG_ERR("SSL_new: %s", ERR_error_string(ERR_get_error(), NULL));
+ retval = ENOPROTOOPT;
+ goto error;
+ }
+ if (SSL_set_fd(ssl, fd) == 0) {
+ VLOG_ERR("SSL_set_fd: %s", ERR_error_string(ERR_get_error(), NULL));
+ retval = ENOPROTOOPT;
+ goto error;
+ }
+ if (bootstrap_ca_cert && type == CLIENT) {
+ SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL);
+ }
+
+ /* Create and return the ssl_stream. */
+ sslv = xmalloc(sizeof *sslv);
+ stream_init(&sslv->stream, &ssl_stream_class, EAGAIN, name);
+ stream_set_remote_ip(&sslv->stream, remote->sin_addr.s_addr);
+ stream_set_remote_port(&sslv->stream, remote->sin_port);
+ stream_set_local_ip(&sslv->stream, local.sin_addr.s_addr);
+ stream_set_local_port(&sslv->stream, local.sin_port);
+ sslv->state = state;
+ sslv->type = type;
+ sslv->fd = fd;
+ sslv->ssl = ssl;
+ sslv->txbuf = NULL;
+ sslv->rx_want = sslv->tx_want = SSL_NOTHING;
+ *streamp = &sslv->stream;
+ return 0;
+
+error:
+ if (ssl) {
+ SSL_free(ssl);
+ }
+ close(fd);
+ return retval;
+}
+
+static struct ssl_stream *
+ssl_stream_cast(struct stream *stream)
+{
+ stream_assert_class(stream, &ssl_stream_class);
+ return CONTAINER_OF(stream, struct ssl_stream, stream);
+}
+
+static int
+ssl_open(const char *name, char *suffix, struct stream **streamp)
+{
+ struct sockaddr_in sin;
+ int error, fd;
+
+ error = ssl_init();
+ if (error) {
+ return error;
+ }
+
+ error = inet_open_active(SOCK_STREAM, suffix, OFP_SSL_PORT, &sin, &fd);
+ if (fd >= 0) {
+ int state = error ? STATE_TCP_CONNECTING : STATE_SSL_CONNECTING;
+ return new_ssl_stream(name, fd, CLIENT, state, &sin, streamp);
+ } else {
+ VLOG_ERR("%s: connect: %s", name, strerror(error));
+ return error;
+ }
+}
+
+static int
+do_ca_cert_bootstrap(struct stream *stream)
+{
+ struct ssl_stream *sslv = ssl_stream_cast(stream);
+ STACK_OF(X509) *chain;
+ X509 *ca_cert;
+ FILE *file;
+ int error;
+ int fd;
+
+ chain = SSL_get_peer_cert_chain(sslv->ssl);
+ if (!chain || !sk_X509_num(chain)) {
+ VLOG_ERR("could not bootstrap CA cert: no certificate presented by "
+ "peer");
+ return EPROTO;
+ }
+ ca_cert = sk_X509_value(chain, sk_X509_num(chain) - 1);
+
+ /* Check that 'ca_cert' is self-signed. Otherwise it is not a CA
+ * certificate and we should not attempt to use it as one. */
+ error = X509_check_issued(ca_cert, ca_cert);
+ if (error) {
+ VLOG_ERR("could not bootstrap CA cert: obtained certificate is "
+ "not self-signed (%s)",
+ X509_verify_cert_error_string(error));
+ if (sk_X509_num(chain) < 2) {
+ VLOG_ERR("only one certificate was received, so probably the peer "
+ "is not configured to send its CA certificate");
+ }
+ return EPROTO;
+ }
+
+ fd = open(ca_cert_file, O_CREAT | O_EXCL | O_WRONLY, 0444);
+ if (fd < 0) {
+ VLOG_ERR("could not bootstrap CA cert: creating %s failed: %s",
+ ca_cert_file, strerror(errno));
+ return errno;
+ }
+
+ file = fdopen(fd, "w");
+ if (!file) {
+ int error = errno;
+ VLOG_ERR("could not bootstrap CA cert: fdopen failed: %s",
+ strerror(error));
+ unlink(ca_cert_file);
+ return error;
+ }
+
+ if (!PEM_write_X509(file, ca_cert)) {
+ VLOG_ERR("could not bootstrap CA cert: PEM_write_X509 to %s failed: "
+ "%s", ca_cert_file, ERR_error_string(ERR_get_error(), NULL));
+ fclose(file);
+ unlink(ca_cert_file);
+ return EIO;
+ }
+
+ if (fclose(file)) {
+ int error = errno;
+ VLOG_ERR("could not bootstrap CA cert: writing %s failed: %s",
+ ca_cert_file, strerror(error));
+ unlink(ca_cert_file);
+ return error;
+ }
+
+ VLOG_INFO("successfully bootstrapped CA cert to %s", ca_cert_file);
+ log_ca_cert(ca_cert_file, ca_cert);
+ bootstrap_ca_cert = false;
+ has_ca_cert = true;
+
+ /* SSL_CTX_add_client_CA makes a copy of ca_cert's relevant data. */
+ SSL_CTX_add_client_CA(ctx, ca_cert);
+
+ /* SSL_CTX_use_certificate() takes ownership of the certificate passed in.
+ * 'ca_cert' is owned by sslv->ssl, so we need to duplicate it. */
+ ca_cert = X509_dup(ca_cert);
+ if (!ca_cert) {
+ out_of_memory();
+ }
+ if (SSL_CTX_load_verify_locations(ctx, ca_cert_file, NULL) != 1) {
+ VLOG_ERR("SSL_CTX_load_verify_locations: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ return EPROTO;
+ }
+ VLOG_INFO("killing successful connection to retry using CA cert");
+ return EPROTO;
+}
+
+static int
+ssl_connect(struct stream *stream)
+{
+ struct ssl_stream *sslv = ssl_stream_cast(stream);
+ int retval;
+
+ switch (sslv->state) {
+ case STATE_TCP_CONNECTING:
+ retval = check_connection_completion(sslv->fd);
+ if (retval) {
+ return retval;
+ }
+ sslv->state = STATE_SSL_CONNECTING;
+ /* Fall through. */
+
+ case STATE_SSL_CONNECTING:
+ retval = (sslv->type == CLIENT
+ ? SSL_connect(sslv->ssl) : SSL_accept(sslv->ssl));
+ if (retval != 1) {
+ int error = SSL_get_error(sslv->ssl, retval);
+ if (retval < 0 && ssl_wants_io(error)) {
+ return EAGAIN;
+ } else {
+ int unused;
+ interpret_ssl_error((sslv->type == CLIENT ? "SSL_connect"
+ : "SSL_accept"), retval, error, &unused);
+ shutdown(sslv->fd, SHUT_RDWR);
+ return EPROTO;
+ }
+ } else if (bootstrap_ca_cert) {
+ return do_ca_cert_bootstrap(stream);
+ } else if ((SSL_get_verify_mode(sslv->ssl)
+ & (SSL_VERIFY_NONE | SSL_VERIFY_PEER))
+ != SSL_VERIFY_PEER) {
+ /* Two or more SSL connections completed at the same time while we
+ * were in bootstrap mode. Only one of these can finish the
+ * bootstrap successfully. The other one(s) must be rejected
+ * because they were not verified against the bootstrapped CA
+ * certificate. (Alternatively we could verify them against the CA
+ * certificate, but that's more trouble than it's worth. These
+ * connections will succeed the next time they retry, assuming that
+ * they have a certificate against the correct CA.) */
+ VLOG_ERR("rejecting SSL connection during bootstrap race window");
+ return EPROTO;
+ } else {
+ return 0;
+ }
+ }
+
+ NOT_REACHED();
+}
+
+static void
+ssl_close(struct stream *stream)
+{
+ struct ssl_stream *sslv = ssl_stream_cast(stream);
+ ssl_clear_txbuf(sslv);
+
+ /* Attempt clean shutdown of the SSL connection. This will work most of
+ * the time, as long as the kernel send buffer has some free space and the
+ * SSL connection isn't renegotiating, etc. That has to be good enough,
+ * since we don't have any way to continue the close operation in the
+ * background. */
+ SSL_shutdown(sslv->ssl);
+
+ SSL_free(sslv->ssl);
+ close(sslv->fd);
+ free(sslv);
+}
+
+static int
+interpret_ssl_error(const char *function, int ret, int error,
+ int *want)
+{
+ *want = SSL_NOTHING;
+
+ switch (error) {
+ case SSL_ERROR_NONE:
+ VLOG_ERR_RL(&rl, "%s: unexpected SSL_ERROR_NONE", function);
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ VLOG_ERR_RL(&rl, "%s: unexpected SSL_ERROR_ZERO_RETURN", function);
+ break;
+
+ case SSL_ERROR_WANT_READ:
+ *want = SSL_READING;
+ return EAGAIN;
+
+ case SSL_ERROR_WANT_WRITE:
+ *want = SSL_WRITING;
+ return EAGAIN;
+
+ case SSL_ERROR_WANT_CONNECT:
+ VLOG_ERR_RL(&rl, "%s: unexpected SSL_ERROR_WANT_CONNECT", function);
+ break;
+
+ case SSL_ERROR_WANT_ACCEPT:
+ VLOG_ERR_RL(&rl, "%s: unexpected SSL_ERROR_WANT_ACCEPT", function);
+ break;
+
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ VLOG_ERR_RL(&rl, "%s: unexpected SSL_ERROR_WANT_X509_LOOKUP",
+ function);
+ break;
+
+ case SSL_ERROR_SYSCALL: {
+ int queued_error = ERR_get_error();
+ if (queued_error == 0) {
+ if (ret < 0) {
+ int status = errno;
+ VLOG_WARN_RL(&rl, "%s: system error (%s)",
+ function, strerror(status));
+ return status;
+ } else {
+ VLOG_WARN_RL(&rl, "%s: unexpected SSL connection close",
+ function);
+ return EPROTO;
+ }
+ } else {
+ VLOG_WARN_RL(&rl, "%s: %s",
+ function, ERR_error_string(queued_error, NULL));
+ break;
+ }
+ }
+
+ case SSL_ERROR_SSL: {
+ int queued_error = ERR_get_error();
+ if (queued_error != 0) {
+ VLOG_WARN_RL(&rl, "%s: %s",
+ function, ERR_error_string(queued_error, NULL));
+ } else {
+ VLOG_ERR_RL(&rl, "%s: SSL_ERROR_SSL without queued error",
+ function);
+ }
+ break;
+ }
+
+ default:
+ VLOG_ERR_RL(&rl, "%s: bad SSL error code %d", function, error);
+ break;
+ }
+ return EIO;
+}
+
+static ssize_t
+ssl_recv(struct stream *stream, void *buffer, size_t n)
+{
+ struct ssl_stream *sslv = ssl_stream_cast(stream);
+ int old_state;
+ ssize_t ret;
+
+ /* Behavior of zero-byte SSL_read is poorly defined. */
+ assert(n > 0);
+
+ old_state = SSL_get_state(sslv->ssl);
+ ret = SSL_read(sslv->ssl, buffer, n);
+ if (old_state != SSL_get_state(sslv->ssl)) {
+ sslv->tx_want = SSL_NOTHING;
+ }
+ sslv->rx_want = SSL_NOTHING;
+
+ if (ret > 0) {
+ return ret;
+ } else {
+ int error = SSL_get_error(sslv->ssl, ret);
+ if (error == SSL_ERROR_ZERO_RETURN) {
+ return 0;
+ } else {
+ return -interpret_ssl_error("SSL_read", ret, error,
+ &sslv->rx_want);
+ }
+ }
+}
+
+static void
+ssl_clear_txbuf(struct ssl_stream *sslv)
+{
+ ofpbuf_delete(sslv->txbuf);
+ sslv->txbuf = NULL;
+}
+
+static int
+ssl_do_tx(struct stream *stream)
+{
+ struct ssl_stream *sslv = ssl_stream_cast(stream);
+
+ for (;;) {
+ int old_state = SSL_get_state(sslv->ssl);
+ int ret = SSL_write(sslv->ssl, sslv->txbuf->data, sslv->txbuf->size);
+ if (old_state != SSL_get_state(sslv->ssl)) {
+ sslv->rx_want = SSL_NOTHING;
+ }
+ sslv->tx_want = SSL_NOTHING;
+ if (ret > 0) {
+ ofpbuf_pull(sslv->txbuf, ret);
+ if (sslv->txbuf->size == 0) {
+ return 0;
+ }
+ } else {
+ int ssl_error = SSL_get_error(sslv->ssl, ret);
+ if (ssl_error == SSL_ERROR_ZERO_RETURN) {
+ VLOG_WARN_RL(&rl, "SSL_write: connection closed");
+ return EPIPE;
+ } else {
+ return interpret_ssl_error("SSL_write", ret, ssl_error,
+ &sslv->tx_want);
+ }
+ }
+ }
+}
+
+static ssize_t
+ssl_send(struct stream *stream, const void *buffer, size_t n)
+{
+ struct ssl_stream *sslv = ssl_stream_cast(stream);
+
+ if (sslv->txbuf) {
+ return -EAGAIN;
+ } else {
+ int error;
+
+ sslv->txbuf = ofpbuf_clone_data(buffer, n);
+ error = ssl_do_tx(stream);
+ switch (error) {
+ case 0:
+ ssl_clear_txbuf(sslv);
+ return n;
+ case EAGAIN:
+ leak_checker_claim(buffer);
+ return n;
+ default:
+ sslv->txbuf = NULL;
+ return -error;
+ }
+ }
+}
+
+static void
+ssl_run(struct stream *stream)
+{
+ struct ssl_stream *sslv = ssl_stream_cast(stream);
+
+ if (sslv->txbuf && ssl_do_tx(stream) != EAGAIN) {
+ ssl_clear_txbuf(sslv);
+ }
+}
+
+static void
+ssl_run_wait(struct stream *stream)
+{
+ struct ssl_stream *sslv = ssl_stream_cast(stream);
+
+ if (sslv->tx_want != SSL_NOTHING) {
+ poll_fd_wait(sslv->fd, want_to_poll_events(sslv->tx_want));
+ }
+}
+
+static void
+ssl_wait(struct stream *stream, enum stream_wait_type wait)
+{
+ struct ssl_stream *sslv = ssl_stream_cast(stream);
+
+ switch (wait) {
+ case STREAM_CONNECT:
+ if (stream_connect(stream) != EAGAIN) {
+ poll_immediate_wake();
+ } else {
+ switch (sslv->state) {
+ case STATE_TCP_CONNECTING:
+ poll_fd_wait(sslv->fd, POLLOUT);
+ break;
+
+ case STATE_SSL_CONNECTING:
+ /* ssl_connect() called SSL_accept() or SSL_connect(), which
+ * set up the status that we test here. */
+ poll_fd_wait(sslv->fd,
+ want_to_poll_events(SSL_want(sslv->ssl)));
+ break;
+
+ default:
+ NOT_REACHED();
+ }
+ }
+ break;
+
+ case STREAM_RECV:
+ if (sslv->rx_want != SSL_NOTHING) {
+ poll_fd_wait(sslv->fd, want_to_poll_events(sslv->rx_want));
+ } else {
+ poll_immediate_wake();
+ }
+ break;
+
+ case STREAM_SEND:
+ if (!sslv->txbuf) {
+ /* We have room in our tx queue. */
+ poll_immediate_wake();
+ } else {
+ /* stream_run_wait() will do the right thing; don't bother with
+ * redundancy. */
+ }
+ break;
+
+ default:
+ NOT_REACHED();
+ }
+}
+
+struct stream_class ssl_stream_class = {
+ "ssl", /* name */
+ ssl_open, /* open */
+ ssl_close, /* close */
+ ssl_connect, /* connect */
+ ssl_recv, /* recv */
+ ssl_send, /* send */
+ ssl_run, /* run */
+ ssl_run_wait, /* run_wait */
+ ssl_wait, /* wait */
+};
+\f
+/* Passive SSL. */
+
+struct pssl_pstream
+{
+ struct pstream pstream;
+ int fd;
+};
+
+struct pstream_class pssl_pstream_class;
+
+static struct pssl_pstream *
+pssl_pstream_cast(struct pstream *pstream)
+{
+ pstream_assert_class(pstream, &pssl_pstream_class);
+ return CONTAINER_OF(pstream, struct pssl_pstream, pstream);
+}
+
+static int
- tmp_dh_callback(SSL *ssl UNUSED, int is_export UNUSED, int keylength)
++pssl_open(const char *name OVS_UNUSED, char *suffix, struct pstream **pstreamp)
+{
+ struct pssl_pstream *pssl;
+ struct sockaddr_in sin;
+ char bound_name[128];
+ int retval;
+ int fd;
+
+ retval = ssl_init();
+ if (retval) {
+ return retval;
+ }
+
+ fd = inet_open_passive(SOCK_STREAM, suffix, OFP_SSL_PORT, &sin);
+ if (fd < 0) {
+ return -fd;
+ }
+ sprintf(bound_name, "pssl:%"PRIu16":"IP_FMT,
+ ntohs(sin.sin_port), IP_ARGS(&sin.sin_addr.s_addr));
+
+ pssl = xmalloc(sizeof *pssl);
+ pstream_init(&pssl->pstream, &pssl_pstream_class, bound_name);
+ pssl->fd = fd;
+ *pstreamp = &pssl->pstream;
+ return 0;
+}
+
+static void
+pssl_close(struct pstream *pstream)
+{
+ struct pssl_pstream *pssl = pssl_pstream_cast(pstream);
+ close(pssl->fd);
+ free(pssl);
+}
+
+static int
+pssl_accept(struct pstream *pstream, struct stream **new_streamp)
+{
+ struct pssl_pstream *pssl = pssl_pstream_cast(pstream);
+ struct sockaddr_in sin;
+ socklen_t sin_len = sizeof sin;
+ char name[128];
+ int new_fd;
+ int error;
+
+ new_fd = accept(pssl->fd, &sin, &sin_len);
+ if (new_fd < 0) {
+ int error = errno;
+ if (error != EAGAIN) {
+ VLOG_DBG_RL(&rl, "accept: %s", strerror(error));
+ }
+ return error;
+ }
+
+ error = set_nonblocking(new_fd);
+ if (error) {
+ close(new_fd);
+ return error;
+ }
+
+ sprintf(name, "ssl:"IP_FMT, IP_ARGS(&sin.sin_addr));
+ if (sin.sin_port != htons(OFP_SSL_PORT)) {
+ sprintf(strchr(name, '\0'), ":%"PRIu16, ntohs(sin.sin_port));
+ }
+ return new_ssl_stream(name, new_fd, SERVER, STATE_SSL_CONNECTING, &sin,
+ new_streamp);
+}
+
+static void
+pssl_wait(struct pstream *pstream)
+{
+ struct pssl_pstream *pssl = pssl_pstream_cast(pstream);
+ poll_fd_wait(pssl->fd, POLLIN);
+}
+
+struct pstream_class pssl_pstream_class = {
+ "pssl",
+ pssl_open,
+ pssl_close,
+ pssl_accept,
+ pssl_wait,
+};
+\f
+/*
+ * Returns true if OpenSSL error is WANT_READ or WANT_WRITE, indicating that
+ * OpenSSL is requesting that we call it back when the socket is ready for read
+ * or writing, respectively.
+ */
+static bool
+ssl_wants_io(int ssl_error)
+{
+ return (ssl_error == SSL_ERROR_WANT_WRITE
+ || ssl_error == SSL_ERROR_WANT_READ);
+}
+
+static int
+ssl_init(void)
+{
+ static int init_status = -1;
+ if (init_status < 0) {
+ init_status = do_ssl_init();
+ assert(init_status >= 0);
+ }
+ return init_status;
+}
+
+static int
+do_ssl_init(void)
+{
+ SSL_METHOD *method;
+
+ SSL_library_init();
+ SSL_load_error_strings();
+
+ method = TLSv1_method();
+ if (method == NULL) {
+ VLOG_ERR("TLSv1_method: %s", ERR_error_string(ERR_get_error(), NULL));
+ return ENOPROTOOPT;
+ }
+
+ ctx = SSL_CTX_new(method);
+ if (ctx == NULL) {
+ VLOG_ERR("SSL_CTX_new: %s", ERR_error_string(ERR_get_error(), NULL));
+ return ENOPROTOOPT;
+ }
+ SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+ SSL_CTX_set_tmp_dh_callback(ctx, tmp_dh_callback);
+ SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
+ SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+ NULL);
+
+ return 0;
+}
+
+static DH *
++tmp_dh_callback(SSL *ssl OVS_UNUSED, int is_export OVS_UNUSED, int keylength)
+{
+ struct dh {
+ int keylength;
+ DH *dh;
+ DH *(*constructor)(void);
+ };
+
+ static struct dh dh_table[] = {
+ {1024, NULL, get_dh1024},
+ {2048, NULL, get_dh2048},
+ {4096, NULL, get_dh4096},
+ };
+
+ struct dh *dh;
+
+ for (dh = dh_table; dh < &dh_table[ARRAY_SIZE(dh_table)]; dh++) {
+ if (dh->keylength == keylength) {
+ if (!dh->dh) {
+ dh->dh = dh->constructor();
+ if (!dh->dh) {
+ ovs_fatal(ENOMEM, "out of memory constructing "
+ "Diffie-Hellman parameters");
+ }
+ }
+ return dh->dh;
+ }
+ }
+ VLOG_ERR_RL(&rl, "no Diffie-Hellman parameters for key length %d",
+ keylength);
+ return NULL;
+}
+
+/* Returns true if SSL is at least partially configured. */
+bool
+stream_ssl_is_configured(void)
+{
+ return has_private_key || has_certificate || has_ca_cert;
+}
+
+void
+stream_ssl_set_private_key_file(const char *file_name)
+{
+ if (ssl_init()) {
+ return;
+ }
+ if (SSL_CTX_use_PrivateKey_file(ctx, file_name, SSL_FILETYPE_PEM) != 1) {
+ VLOG_ERR("SSL_use_PrivateKey_file: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ return;
+ }
+ has_private_key = true;
+}
+
+void
+stream_ssl_set_certificate_file(const char *file_name)
+{
+ if (ssl_init()) {
+ return;
+ }
+ if (SSL_CTX_use_certificate_chain_file(ctx, file_name) != 1) {
+ VLOG_ERR("SSL_use_certificate_file: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ return;
+ }
+ has_certificate = true;
+}
+
+/* Reads the X509 certificate or certificates in file 'file_name'. On success,
+ * stores the address of the first element in an array of pointers to
+ * certificates in '*certs' and the number of certificates in the array in
+ * '*n_certs', and returns 0. On failure, stores a null pointer in '*certs', 0
+ * in '*n_certs', and returns a positive errno value.
+ *
+ * The caller is responsible for freeing '*certs'. */
+static int
+read_cert_file(const char *file_name, X509 ***certs, size_t *n_certs)
+{
+ FILE *file;
+ size_t allocated_certs = 0;
+
+ *certs = NULL;
+ *n_certs = 0;
+
+ file = fopen(file_name, "r");
+ if (!file) {
+ VLOG_ERR("failed to open %s for reading: %s",
+ file_name, strerror(errno));
+ return errno;
+ }
+
+ for (;;) {
+ X509 *certificate;
+ int c;
+
+ /* Read certificate from file. */
+ certificate = PEM_read_X509(file, NULL, NULL, NULL);
+ if (!certificate) {
+ size_t i;
+
+ VLOG_ERR("PEM_read_X509 failed reading %s: %s",
+ file_name, ERR_error_string(ERR_get_error(), NULL));
+ for (i = 0; i < *n_certs; i++) {
+ X509_free((*certs)[i]);
+ }
+ free(*certs);
+ *certs = NULL;
+ *n_certs = 0;
+ return EIO;
+ }
+
+ /* Add certificate to array. */
+ if (*n_certs >= allocated_certs) {
+ *certs = x2nrealloc(*certs, &allocated_certs, sizeof **certs);
+ }
+ (*certs)[(*n_certs)++] = certificate;
+
+ /* Are there additional certificates in the file? */
+ do {
+ c = getc(file);
+ } while (isspace(c));
+ if (c == EOF) {
+ break;
+ }
+ ungetc(c, file);
+ }
+ fclose(file);
+ return 0;
+}
+
+
+/* Sets 'file_name' as the name of a file containing one or more X509
+ * certificates to send to the peer. Typical use in OpenFlow is to send the CA
+ * certificate to the peer, which enables a switch to pick up the controller's
+ * CA certificate on its first connection. */
+void
+stream_ssl_set_peer_ca_cert_file(const char *file_name)
+{
+ X509 **certs;
+ size_t n_certs;
+ size_t i;
+
+ if (ssl_init()) {
+ return;
+ }
+
+ if (!read_cert_file(file_name, &certs, &n_certs)) {
+ for (i = 0; i < n_certs; i++) {
+ if (SSL_CTX_add_extra_chain_cert(ctx, certs[i]) != 1) {
+ VLOG_ERR("SSL_CTX_add_extra_chain_cert: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ }
+ }
+ free(certs);
+ }
+}
+
+/* Logs fingerprint of CA certificate 'cert' obtained from 'file_name'. */
+static void
+log_ca_cert(const char *file_name, X509 *cert)
+{
+ unsigned char digest[EVP_MAX_MD_SIZE];
+ unsigned int n_bytes;
+ struct ds fp;
+ char *subject;
+
+ ds_init(&fp);
+ if (!X509_digest(cert, EVP_sha1(), digest, &n_bytes)) {
+ ds_put_cstr(&fp, "<out of memory>");
+ } else {
+ unsigned int i;
+ for (i = 0; i < n_bytes; i++) {
+ if (i) {
+ ds_put_char(&fp, ':');
+ }
+ ds_put_format(&fp, "%02hhx", digest[i]);
+ }
+ }
+ subject = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
+ VLOG_INFO("Trusting CA cert from %s (%s) (fingerprint %s)", file_name,
+ subject ? subject : "<out of memory>", ds_cstr(&fp));
+ free(subject);
+ ds_destroy(&fp);
+}
+
+/* Sets 'file_name' as the name of the file from which to read the CA
+ * certificate used to verify the peer within SSL connections. If 'bootstrap'
+ * is false, the file must exist. If 'bootstrap' is false, then the file is
+ * read if it is exists; if it does not, then it will be created from the CA
+ * certificate received from the peer on the first SSL connection. */
+void
+stream_ssl_set_ca_cert_file(const char *file_name, bool bootstrap)
+{
+ X509 **certs;
+ size_t n_certs;
+ struct stat s;
+
+ if (ssl_init()) {
+ return;
+ }
+
+ if (bootstrap && stat(file_name, &s) && errno == ENOENT) {
+ bootstrap_ca_cert = true;
+ ca_cert_file = xstrdup(file_name);
+ } else if (!read_cert_file(file_name, &certs, &n_certs)) {
+ size_t i;
+
+ /* Set up list of CAs that the server will accept from the client. */
+ for (i = 0; i < n_certs; i++) {
+ /* SSL_CTX_add_client_CA makes a copy of the relevant data. */
+ if (SSL_CTX_add_client_CA(ctx, certs[i]) != 1) {
+ VLOG_ERR("failed to add client certificate %d from %s: %s",
+ i, file_name,
+ ERR_error_string(ERR_get_error(), NULL));
+ } else {
+ log_ca_cert(file_name, certs[i]);
+ }
+ X509_free(certs[i]);
+ }
+ free(certs);
+
+ /* Set up CAs for OpenSSL to trust in verifying the peer's
+ * certificate. */
+ if (SSL_CTX_load_verify_locations(ctx, file_name, NULL) != 1) {
+ VLOG_ERR("SSL_CTX_load_verify_locations: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ return;
+ }
+
+ has_ca_cert = true;
+ }
+}
--- /dev/null
- ptcp_open(const char *name UNUSED, char *suffix, struct pstream **pstreamp)
+/*
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "stream.h"
+#include <errno.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "packets.h"
+#include "socket-util.h"
+#include "util.h"
+#include "stream-provider.h"
+#include "stream-fd.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_stream_tcp
+
+/* Active TCP. */
+
+static int
+new_tcp_stream(const char *name, int fd, int connect_status,
+ const struct sockaddr_in *remote, struct stream **streamp)
+{
+ struct sockaddr_in local;
+ socklen_t local_len = sizeof local;
+ int on = 1;
+ int retval;
+
+ /* Get the local IP and port information */
+ retval = getsockname(fd, (struct sockaddr *)&local, &local_len);
+ if (retval) {
+ memset(&local, 0, sizeof local);
+ }
+
+ retval = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof on);
+ if (retval) {
+ VLOG_ERR("%s: setsockopt(TCP_NODELAY): %s", name, strerror(errno));
+ close(fd);
+ return errno;
+ }
+
+ retval = new_fd_stream(name, fd, connect_status, NULL, streamp);
+ if (!retval) {
+ struct stream *stream = *streamp;
+ stream_set_remote_ip(stream, remote->sin_addr.s_addr);
+ stream_set_remote_port(stream, remote->sin_port);
+ stream_set_local_ip(stream, local.sin_addr.s_addr);
+ stream_set_local_port(stream, local.sin_port);
+ }
+ return retval;
+}
+
+static int
+tcp_open(const char *name, char *suffix, struct stream **streamp)
+{
+ struct sockaddr_in sin;
+ int fd, error;
+
+ error = inet_open_active(SOCK_STREAM, suffix, 0, &sin, &fd);
+ if (fd >= 0) {
+ return new_tcp_stream(name, fd, error, &sin, streamp);
+ } else {
+ VLOG_ERR("%s: connect: %s", name, strerror(error));
+ return error;
+ }
+}
+
+struct stream_class tcp_stream_class = {
+ "tcp", /* name */
+ tcp_open, /* open */
+ NULL, /* close */
+ NULL, /* connect */
+ NULL, /* recv */
+ NULL, /* send */
+ NULL, /* run */
+ NULL, /* run_wait */
+ NULL, /* wait */
+};
+\f
+/* Passive TCP. */
+
+static int ptcp_accept(int fd, const struct sockaddr *sa, size_t sa_len,
+ struct stream **streamp);
+
+static int
++ptcp_open(const char *name OVS_UNUSED, char *suffix, struct pstream **pstreamp)
+{
+ struct sockaddr_in sin;
+ char bound_name[128];
+ int fd;
+
+ fd = inet_open_passive(SOCK_STREAM, suffix, -1, &sin);
+ if (fd < 0) {
+ return -fd;
+ }
+
+ sprintf(bound_name, "ptcp:%"PRIu16":"IP_FMT,
+ ntohs(sin.sin_port), IP_ARGS(&sin.sin_addr.s_addr));
+ return new_fd_pstream(bound_name, fd, ptcp_accept, NULL, pstreamp);
+}
+
+static int
+ptcp_accept(int fd, const struct sockaddr *sa, size_t sa_len,
+ struct stream **streamp)
+{
+ const struct sockaddr_in *sin = (const struct sockaddr_in *) sa;
+ char name[128];
+
+ if (sa_len == sizeof(struct sockaddr_in) && sin->sin_family == AF_INET) {
+ sprintf(name, "tcp:"IP_FMT, IP_ARGS(&sin->sin_addr));
+ sprintf(strchr(name, '\0'), ":%"PRIu16, ntohs(sin->sin_port));
+ } else {
+ strcpy(name, "tcp");
+ }
+ return new_tcp_stream(name, fd, 0, sin, streamp);
+}
+
+struct pstream_class ptcp_pstream_class = {
+ "ptcp",
+ ptcp_open,
+ NULL,
+ NULL,
+ NULL
+};
+
--- /dev/null
- * Copyright (c) 2008, 2009 Nicira Networks.
+/*
- punix_open(const char *name UNUSED, char *suffix, struct pstream **pstreamp)
++ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "stream.h"
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <netdb.h>
+#include <poll.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "packets.h"
+#include "poll-loop.h"
+#include "socket-util.h"
+#include "util.h"
+#include "stream-provider.h"
+#include "stream-fd.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_stream_unix
+
+/* Active UNIX socket. */
+
+/* Number of unix sockets created so far, to ensure binding path uniqueness. */
+static int n_unix_sockets;
+
+static int
+unix_open(const char *name, char *suffix, struct stream **streamp)
+{
+ const char *connect_path = suffix;
+ char *bind_path;
+ int fd;
+
+ bind_path = xasprintf("/tmp/stream-unix.%ld.%d",
+ (long int) getpid(), n_unix_sockets++);
+ fd = make_unix_socket(SOCK_STREAM, true, false, bind_path, connect_path);
+ if (fd < 0) {
+ VLOG_ERR("%s: connection to %s failed: %s",
+ bind_path, connect_path, strerror(-fd));
+ free(bind_path);
+ return -fd;
+ }
+
+ return new_fd_stream(name, fd, check_connection_completion(fd),
+ bind_path, streamp);
+}
+
+struct stream_class unix_stream_class = {
+ "unix", /* name */
+ unix_open, /* open */
+ NULL, /* close */
+ NULL, /* connect */
+ NULL, /* recv */
+ NULL, /* send */
+ NULL, /* run */
+ NULL, /* run_wait */
+ NULL, /* wait */
+};
+\f
+/* Passive UNIX socket. */
+
+static int punix_accept(int fd, const struct sockaddr *sa, size_t sa_len,
+ struct stream **streamp);
+
+static int
++punix_open(const char *name OVS_UNUSED, char *suffix,
++ struct pstream **pstreamp)
+{
+ int fd, error;
+
+ fd = make_unix_socket(SOCK_STREAM, true, true, suffix, NULL);
+ if (fd < 0) {
+ VLOG_ERR("%s: binding failed: %s", suffix, strerror(errno));
+ return errno;
+ }
+
+ if (listen(fd, 10) < 0) {
+ error = errno;
+ VLOG_ERR("%s: listen: %s", name, strerror(error));
+ close(fd);
+ return error;
+ }
+
+ return new_fd_pstream("punix", fd, punix_accept,
+ xstrdup(suffix), pstreamp);
+}
+
+static int
+punix_accept(int fd, const struct sockaddr *sa, size_t sa_len,
+ struct stream **streamp)
+{
+ const struct sockaddr_un *sun = (const struct sockaddr_un *) sa;
+ int name_len = get_unix_name_len(sa_len);
+ char name[128];
+
+ if (name_len > 0) {
+ snprintf(name, sizeof name, "unix:%.*s", name_len, sun->sun_path);
+ } else {
+ strcpy(name, "unix");
+ }
+ return new_fd_stream(name, fd, 0, NULL, streamp);
+}
+
+struct pstream_class punix_pstream_class = {
+ "punix",
+ punix_open,
+ NULL,
+ NULL,
+ NULL
+};
+
--- /dev/null
- bool bootstrap UNUSED)
+/*
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "stream-provider.h"
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include "coverage.h"
+#include "dynamic-string.h"
+#include "flow.h"
+#include "ofp-print.h"
+#include "ofpbuf.h"
+#include "openflow/nicira-ext.h"
+#include "openflow/openflow.h"
+#include "packets.h"
+#include "poll-loop.h"
+#include "random.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_stream
+#include "vlog.h"
+
+/* State of an active stream.*/
+enum stream_state {
+ SCS_CONNECTING, /* Underlying stream is not connected. */
+ SCS_CONNECTED, /* Connection established. */
+ SCS_DISCONNECTED /* Connection failed or connection closed. */
+};
+
+static struct stream_class *stream_classes[] = {
+ &tcp_stream_class,
+ &unix_stream_class,
+#ifdef HAVE_OPENSSL
+ &ssl_stream_class,
+#endif
+};
+
+static struct pstream_class *pstream_classes[] = {
+ &ptcp_pstream_class,
+ &punix_pstream_class,
+#ifdef HAVE_OPENSSL
+ &pssl_pstream_class,
+#endif
+};
+
+/* Check the validity of the stream class structures. */
+static void
+check_stream_classes(void)
+{
+#ifndef NDEBUG
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(stream_classes); i++) {
+ struct stream_class *class = stream_classes[i];
+ assert(class->name != NULL);
+ assert(class->open != NULL);
+ if (class->close || class->recv || class->send || class->run
+ || class->run_wait || class->wait) {
+ assert(class->close != NULL);
+ assert(class->recv != NULL);
+ assert(class->send != NULL);
+ assert(class->wait != NULL);
+ } else {
+ /* This class delegates to another one. */
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(pstream_classes); i++) {
+ struct pstream_class *class = pstream_classes[i];
+ assert(class->name != NULL);
+ assert(class->listen != NULL);
+ if (class->close || class->accept || class->wait) {
+ assert(class->close != NULL);
+ assert(class->accept != NULL);
+ assert(class->wait != NULL);
+ } else {
+ /* This class delegates to another one. */
+ }
+ }
+#endif
+}
+
+/* Prints information on active (if 'active') and passive (if 'passive')
+ * connection methods supported by the stream. */
+void
+stream_usage(const char *name, bool active, bool passive,
++ bool bootstrap OVS_UNUSED)
+{
+ /* Really this should be implemented via callbacks into the stream
+ * providers, but that seems too heavy-weight to bother with at the
+ * moment. */
+
+ printf("\n");
+ if (active) {
+ printf("Active %s connection methods:\n", name);
+ printf(" tcp:IP:PORT "
+ "PORT at remote IP\n");
+#ifdef HAVE_OPENSSL
+ printf(" ssl:IP:PORT "
+ "SSL PORT at remote IP\n");
+#endif
+ printf(" unix:FILE "
+ "Unix domain socket named FILE\n");
+ }
+
+ if (passive) {
+ printf("Passive %s connection methods:\n", name);
+ printf(" ptcp:PORT[:IP] "
+ "listen to TCP PORT on IP\n");
+#ifdef HAVE_OPENSSL
+ printf(" pssl:PORT[:IP] "
+ "listen for SSL on PORT on IP\n");
+#endif
+ printf(" punix:FILE "
+ "listen on Unix domain socket FILE\n");
+ }
+
+#ifdef HAVE_OPENSSL
+ printf("PKI configuration (required to use SSL):\n"
+ " -p, --private-key=FILE file with private key\n"
+ " -c, --certificate=FILE file with certificate for private key\n"
+ " -C, --ca-cert=FILE file with peer CA certificate\n");
+ if (bootstrap) {
+ printf(" --bootstrap-ca-cert=FILE file with peer CA certificate "
+ "to read or create\n");
+ }
+#endif
+}
+
+/* Attempts to connect a stream to a remote peer. 'name' is a connection name
+ * in the form "TYPE:ARGS", where TYPE is an active stream class's name and
+ * ARGS are stream class-specific.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. If successful,
+ * stores a pointer to the new connection in '*streamp', otherwise a null
+ * pointer. */
+int
+stream_open(const char *name, struct stream **streamp)
+{
+ size_t prefix_len;
+ size_t i;
+
+ COVERAGE_INC(stream_open);
+ check_stream_classes();
+
+ *streamp = NULL;
+ prefix_len = strcspn(name, ":");
+ if (prefix_len == strlen(name)) {
+ return EAFNOSUPPORT;
+ }
+ for (i = 0; i < ARRAY_SIZE(stream_classes); i++) {
+ struct stream_class *class = stream_classes[i];
+ if (strlen(class->name) == prefix_len
+ && !memcmp(class->name, name, prefix_len)) {
+ struct stream *stream;
+ char *suffix_copy = xstrdup(name + prefix_len + 1);
+ int retval = class->open(name, suffix_copy, &stream);
+ free(suffix_copy);
+ if (!retval) {
+ assert(stream->state != SCS_CONNECTING
+ || stream->class->connect);
+ *streamp = stream;
+ }
+ return retval;
+ }
+ }
+ return EAFNOSUPPORT;
+}
+
+int
+stream_open_block(const char *name, struct stream **streamp)
+{
+ struct stream *stream;
+ int error;
+
+ error = stream_open(name, &stream);
+ while (error == EAGAIN) {
+ stream_run(stream);
+ stream_run_wait(stream);
+ stream_connect_wait(stream);
+ poll_block();
+ error = stream_connect(stream);
+ assert(error != EINPROGRESS);
+ }
+ if (error) {
+ stream_close(stream);
+ *streamp = NULL;
+ } else {
+ *streamp = stream;
+ }
+ return error;
+}
+
+/* Closes 'stream'. */
+void
+stream_close(struct stream *stream)
+{
+ if (stream != NULL) {
+ char *name = stream->name;
+ (stream->class->close)(stream);
+ free(name);
+ }
+}
+
+/* Returns the name of 'stream', that is, the string passed to
+ * stream_open(). */
+const char *
+stream_get_name(const struct stream *stream)
+{
+ return stream ? stream->name : "(null)";
+}
+
+/* Returns the IP address of the peer, or 0 if the peer is not connected over
+ * an IP-based protocol or if its IP address is not yet known. */
+uint32_t
+stream_get_remote_ip(const struct stream *stream)
+{
+ return stream->remote_ip;
+}
+
+/* Returns the transport port of the peer, or 0 if the connection does not
+ * contain a port or if the port is not yet known. */
+uint16_t
+stream_get_remote_port(const struct stream *stream)
+{
+ return stream->remote_port;
+}
+
+/* Returns the IP address used to connect to the peer, or 0 if the connection
+ * is not an IP-based protocol or if its IP address is not yet known. */
+uint32_t
+stream_get_local_ip(const struct stream *stream)
+{
+ return stream->local_ip;
+}
+
+/* Returns the transport port used to connect to the peer, or 0 if the
+ * connection does not contain a port or if the port is not yet known. */
+uint16_t
+stream_get_local_port(const struct stream *stream)
+{
+ return stream->local_port;
+}
+
+static void
+scs_connecting(struct stream *stream)
+{
+ int retval = (stream->class->connect)(stream);
+ assert(retval != EINPROGRESS);
+ if (!retval) {
+ stream->state = SCS_CONNECTED;
+ } else if (retval != EAGAIN) {
+ stream->state = SCS_DISCONNECTED;
+ stream->error = retval;
+ }
+}
+
+/* Tries to complete the connection on 'stream', which must be an active
+ * stream. If 'stream''s connection is complete, returns 0 if the connection
+ * was successful or a positive errno value if it failed. If the
+ * connection is still in progress, returns EAGAIN. */
+int
+stream_connect(struct stream *stream)
+{
+ enum stream_state last_state;
+
+ do {
+ last_state = stream->state;
+ switch (stream->state) {
+ case SCS_CONNECTING:
+ scs_connecting(stream);
+ break;
+
+ case SCS_CONNECTED:
+ return 0;
+
+ case SCS_DISCONNECTED:
+ return stream->error;
+
+ default:
+ NOT_REACHED();
+ }
+ } while (stream->state != last_state);
+
+ return EAGAIN;
+}
+
+/* Tries to receive up to 'n' bytes from 'stream' into 'buffer', and returns:
+ *
+ * - If successful, the number of bytes received (between 1 and 'n').
+ *
+ * - On error, a negative errno value.
+ *
+ * - 0, if the connection has been closed in the normal fashion, or if 'n'
+ * is zero.
+ *
+ * The recv function will not block waiting for a packet to arrive. If no
+ * data have been received, it returns -EAGAIN immediately. */
+int
+stream_recv(struct stream *stream, void *buffer, size_t n)
+{
+ int retval = stream_connect(stream);
+ return (retval ? -retval
+ : n == 0 ? 0
+ : (stream->class->recv)(stream, buffer, n));
+}
+
+/* Tries to send up to 'n' bytes of 'buffer' on 'stream', and returns:
+ *
+ * - If successful, the number of bytes sent (between 1 and 'n'). 0 is
+ * only a valid return value if 'n' is 0.
+ *
+ * - On error, a negative errno value.
+ *
+ * The send function will not block. If no bytes can be immediately accepted
+ * for transmission, it returns -EAGAIN immediately. */
+int
+stream_send(struct stream *stream, const void *buffer, size_t n)
+{
+ int retval = stream_connect(stream);
+ return (retval ? -retval
+ : n == 0 ? 0
+ : (stream->class->send)(stream, buffer, n));
+}
+
+/* Allows 'stream' to perform maintenance activities, such as flushing
+ * output buffers. */
+void
+stream_run(struct stream *stream)
+{
+ if (stream->class->run) {
+ (stream->class->run)(stream);
+ }
+}
+
+/* Arranges for the poll loop to wake up when 'stream' needs to perform
+ * maintenance activities. */
+void
+stream_run_wait(struct stream *stream)
+{
+ if (stream->class->run_wait) {
+ (stream->class->run_wait)(stream);
+ }
+}
+
+/* Arranges for the poll loop to wake up when 'stream' is ready to take an
+ * action of the given 'type'. */
+void
+stream_wait(struct stream *stream, enum stream_wait_type wait)
+{
+ assert(wait == STREAM_CONNECT || wait == STREAM_RECV
+ || wait == STREAM_SEND);
+
+ switch (stream->state) {
+ case SCS_CONNECTING:
+ wait = STREAM_CONNECT;
+ break;
+
+ case SCS_DISCONNECTED:
+ poll_immediate_wake();
+ return;
+ }
+ (stream->class->wait)(stream, wait);
+}
+
+void
+stream_connect_wait(struct stream *stream)
+{
+ stream_wait(stream, STREAM_CONNECT);
+}
+
+void
+stream_recv_wait(struct stream *stream)
+{
+ stream_wait(stream, STREAM_RECV);
+}
+
+void
+stream_send_wait(struct stream *stream)
+{
+ stream_wait(stream, STREAM_SEND);
+}
+
+/* Attempts to start listening for remote stream connections. 'name' is a
+ * connection name in the form "TYPE:ARGS", where TYPE is an passive stream
+ * class's name and ARGS are stream class-specific.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. If successful,
+ * stores a pointer to the new connection in '*pstreamp', otherwise a null
+ * pointer. */
+int
+pstream_open(const char *name, struct pstream **pstreamp)
+{
+ size_t prefix_len;
+ size_t i;
+
+ check_stream_classes();
+
+ *pstreamp = NULL;
+ prefix_len = strcspn(name, ":");
+ if (prefix_len == strlen(name)) {
+ return EAFNOSUPPORT;
+ }
+ for (i = 0; i < ARRAY_SIZE(pstream_classes); i++) {
+ struct pstream_class *class = pstream_classes[i];
+ if (strlen(class->name) == prefix_len
+ && !memcmp(class->name, name, prefix_len)) {
+ char *suffix_copy = xstrdup(name + prefix_len + 1);
+ int retval = class->listen(name, suffix_copy, pstreamp);
+ free(suffix_copy);
+ if (retval) {
+ *pstreamp = NULL;
+ }
+ return retval;
+ }
+ }
+ return EAFNOSUPPORT;
+}
+
+/* Returns the name that was used to open 'pstream'. The caller must not
+ * modify or free the name. */
+const char *
+pstream_get_name(const struct pstream *pstream)
+{
+ return pstream->name;
+}
+
+/* Closes 'pstream'. */
+void
+pstream_close(struct pstream *pstream)
+{
+ if (pstream != NULL) {
+ char *name = pstream->name;
+ (pstream->class->close)(pstream);
+ free(name);
+ }
+}
+
+/* Tries to accept a new connection on 'pstream'. If successful, stores the
+ * new connection in '*new_stream' and returns 0. Otherwise, returns a
+ * positive errno value.
+ *
+ * pstream_accept() will not block waiting for a connection. If no connection
+ * is ready to be accepted, it returns EAGAIN immediately. */
+int
+pstream_accept(struct pstream *pstream, struct stream **new_stream)
+{
+ int retval = (pstream->class->accept)(pstream, new_stream);
+ if (retval) {
+ *new_stream = NULL;
+ } else {
+ assert((*new_stream)->state != SCS_CONNECTING
+ || (*new_stream)->class->connect);
+ }
+ return retval;
+}
+
+/* Tries to accept a new connection on 'pstream'. If successful, stores the
+ * new connection in '*new_stream' and returns 0. Otherwise, returns a
+ * positive errno value.
+ *
+ * pstream_accept_block() blocks until a connection is ready or until an error
+ * occurs. It will not return EAGAIN. */
+int
+pstream_accept_block(struct pstream *pstream, struct stream **new_stream)
+{
+ int error;
+
+ while ((error = pstream_accept(pstream, new_stream)) == EAGAIN) {
+ pstream_wait(pstream);
+ poll_block();
+ }
+ if (error) {
+ *new_stream = NULL;
+ }
+ return error;
+}
+
+void
+pstream_wait(struct pstream *pstream)
+{
+ (pstream->class->wait)(pstream);
+}
+\f
+/* Initializes 'stream' as a new stream named 'name', implemented via 'class'.
+ * The initial connection status, supplied as 'connect_status', is interpreted
+ * as follows:
+ *
+ * - 0: 'stream' is connected. Its 'send' and 'recv' functions may be
+ * called in the normal fashion.
+ *
+ * - EAGAIN: 'stream' is trying to complete a connection. Its 'connect'
+ * function should be called to complete the connection.
+ *
+ * - Other positive errno values indicate that the connection failed with
+ * the specified error.
+ *
+ * After calling this function, stream_close() must be used to destroy
+ * 'stream', otherwise resources will be leaked.
+ *
+ * The caller retains ownership of 'name'. */
+void
+stream_init(struct stream *stream, struct stream_class *class,
+ int connect_status, const char *name)
+{
+ stream->class = class;
+ stream->state = (connect_status == EAGAIN ? SCS_CONNECTING
+ : !connect_status ? SCS_CONNECTED
+ : SCS_DISCONNECTED);
+ stream->error = connect_status;
+ stream->name = xstrdup(name);
+ assert(stream->state != SCS_CONNECTING || class->connect);
+}
+
+void
+stream_set_remote_ip(struct stream *stream, uint32_t ip)
+{
+ stream->remote_ip = ip;
+}
+
+void
+stream_set_remote_port(struct stream *stream, uint16_t port)
+{
+ stream->remote_port = port;
+}
+
+void
+stream_set_local_ip(struct stream *stream, uint32_t ip)
+{
+ stream->local_ip = ip;
+}
+
+void
+stream_set_local_port(struct stream *stream, uint16_t port)
+{
+ stream->local_port = port;
+}
+
+void
+pstream_init(struct pstream *pstream, struct pstream_class *class,
+ const char *name)
+{
+ pstream->class = class;
+ pstream->name = xstrdup(name);
+}
/*
-- * Copyright (c) 2008, 2009 Nicira Networks.
++ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
void time_alarm(unsigned int secs);
int time_poll(struct pollfd *, int n_pollfds, int timeout);
+long long int timeval_to_msec(const struct timeval *);
+
+ #ifdef __cplusplus
+ }
+ #endif
+
#endif /* timeval.h */
static struct shash commands = SHASH_INITIALIZER(&commands);
static void
- unixctl_help(struct unixctl_conn *conn, const char *args UNUSED,
- void *aux UNUSED)
-unixctl_help(struct unixctl_conn *conn, const char *args OVS_UNUSED)
++unixctl_help(struct unixctl_conn *conn, const char *args OVS_UNUSED,
++ void *aux OVS_UNUSED)
{
struct ds ds = DS_EMPTY_INITIALIZER;
struct shash_node *node;
/*
-- * Copyright (c) 2008, 2009 Nicira Networks.
++ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
{
return str_to_llong(s, base, (long long *) ull);
}
- void ignore(bool x UNUSED) { }
+
+/* Converts floating-point string 's' into a double. If successful, stores
+ * the double in '*d' and returns true; on failure, stores 0 in '*d' and
+ * returns false.
+ *
+ * Underflow (e.g. "1e-9999") is not considered an error, but overflow
+ * (e.g. "1e9999)" is. */
+bool
+str_to_double(const char *s, double *d)
+{
+ int save_errno = errno;
+ char *tail;
+ errno = 0;
+ *d = strtod(s, &tail);
+ if (errno == EINVAL || (errno == ERANGE && *d != 0)
+ || tail == s || *tail != '\0') {
+ errno = save_errno;
+ *d = 0;
+ return false;
+ } else {
+ errno = save_errno;
+ return true;
+ }
+}
+
+/* Returns the value of 'c' as a hexadecimal digit. */
+int
+hexit_value(int c)
+{
+ switch (c) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ return c - '0';
+
+ case 'a': case 'A':
+ return 0xa;
+
+ case 'b': case 'B':
+ return 0xb;
+
+ case 'c': case 'C':
+ return 0xc;
+
+ case 'd': case 'D':
+ return 0xd;
+
+ case 'e': case 'E':
+ return 0xe;
+
+ case 'f': case 'F':
+ return 0xf;
+ }
+
+ NOT_REACHED();
+}
+
+/* Returns the directory name portion of 'file_name' as a malloc()'d string,
+ * similar to the POSIX dirname() function but thread-safe. */
+char *
+dir_name(const char *file_name)
+{
+ size_t len = strlen(file_name);
+ while (len > 0 && file_name[len - 1] == '/') {
+ len--;
+ }
+ while (len > 0 && file_name[len - 1] != '/') {
+ len--;
+ }
+ while (len > 0 && file_name[len - 1] == '/') {
+ len--;
+ }
+ if (!len) {
+ return xstrdup((file_name[0] == '/'
+ && file_name[1] == '/'
+ && file_name[2] != '/') ? "//"
+ : file_name[0] == '/' ? "/"
+ : ".");
+ } else {
+ return xmemdup0(file_name, len);
+ }
+}
+
+/* Pass a value to this function if it is marked with
+ * __attribute__((warn_unused_result)) and you genuinely want to ignore
+ * its return value. (Note that every scalar type can be implicitly
+ * converted to bool.) */
++void ignore(bool x OVS_UNUSED) { }
/*
-- * Copyright (c) 2008, 2009 Nicira Networks.
++ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
bool str_to_ulong(const char *, int base, unsigned long *);
bool str_to_ullong(const char *, int base, unsigned long long *);
- void ignore(bool x UNUSED);
+bool str_to_double(const char *, double *);
+
+int hexit_value(int c);
+
+char *dir_name(const char *file_name);
+
++void ignore(bool x OVS_UNUSED);
+
#ifdef __cplusplus
}
#endif
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 25);
-static void stream_clear_txbuf(struct stream_vconn *);
-static void maybe_unlink_and_free(char *path);
+static void vconn_stream_clear_txbuf(struct vconn_stream *);
+static int count_fields(const char *);
-/* Creates a new vconn named 'name' that will send and receive data on 'fd' and
- * stores a pointer to the vconn in '*vconnp'. Initial connection status
- * 'connect_status' is interpreted as described for vconn_init().
- *
- * When '*vconnp' is closed, then 'unlink_path' (if nonnull) will be passed to
- * fatal_signal_unlink_file_now() and then freed with free().
- *
- * Returns 0 if successful, otherwise a positive errno value. (The current
- * implementation never fails.) */
-int
-new_stream_vconn(const char *name, int fd, int connect_status,
- char *unlink_path, struct vconn **vconnp)
+static struct vconn *
+vconn_stream_new(struct stream *stream, int connect_status)
{
- struct stream_vconn *s;
+ struct vconn_stream *s;
s = xmalloc(sizeof *s);
- vconn_init(&s->vconn, &stream_vconn_class, connect_status, name);
- s->fd = fd;
+ vconn_init(&s->vconn, &stream_vconn_class, connect_status,
+ stream_get_name(stream));
+ s->stream = stream;
s->txbuf = NULL;
- s->tx_waiter = NULL;
s->rxbuf = NULL;
- s->unlink_path = unlink_path;
- *vconnp = &s->vconn;
+ return &s->vconn;
+}
+
+/* Creates a new vconn that will send and receive data on a stream named 'name'
+ * and stores a pointer to the vconn in '*vconnp'.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. */
+static int
- vconn_stream_open(const char *name_, char *suffix UNUSED,
++vconn_stream_open(const char *name_, char *suffix OVS_UNUSED,
+ struct vconn **vconnp)
+{
+ struct stream *stream;
+ char *name;
+ int error;
+
+ if (!strncmp(name_, "tcp:", 4) && count_fields(name_) < 3) {
+ name = xasprintf("%s:%d", name_, OFP_TCP_PORT);
+ } else if (!strncmp(name_, "ssl:", 4) && count_fields(name_) < 3) {
+ name = xasprintf("%s:%d", name_, OFP_SSL_PORT);
+ } else {
+ name = xstrdup(name_);
+ }
+ error = stream_open(name, &stream);
+ free(name);
+
+ if (error && error != EAGAIN) {
+ return error;
+ }
+
+ *vconnp = vconn_stream_new(stream, error);
return 0;
}
*
* Returns 0 if successful, otherwise a positive errno value. (The current
* implementation never fails.) */
-int
-new_pstream_pvconn(const char *name, int fd,
- int (*accept_cb)(int fd, const struct sockaddr *sa,
- size_t sa_len, struct vconn **vconnp),
- char *unlink_path, struct pvconn **pvconnp)
+static int
- pvconn_pstream_listen(const char *name_, char *suffix UNUSED,
++pvconn_pstream_listen(const char *name_, char *suffix OVS_UNUSED,
+ struct pvconn **pvconnp)
{
- struct pstream_pvconn *ps = xmalloc(sizeof *ps);
- pvconn_init(&ps->pvconn, &pstream_pvconn_class, name);
- ps->fd = fd;
- ps->accept_cb = accept_cb;
- ps->unlink_path = unlink_path;
+ struct pvconn_pstream *ps;
+ struct pstream *pstream;
+ char *name;
+ int error;
+
+ if (!strncmp(name_, "ptcp:", 5) && count_fields(name_) < 2) {
+ name = xasprintf("%s:%d", name_, OFP_TCP_PORT);
+ } else if (!strncmp(name_, "pssl:", 5) && count_fields(name_) < 2) {
+ name = xasprintf("%s:%d", name_, OFP_SSL_PORT);
+ } else {
+ name = xstrdup(name_);
+ }
+ error = pstream_open(name, &pstream);
+ free(name);
+ if (error) {
+ return error;
+ }
+
+ ps = xmalloc(sizeof *ps);
+ pvconn_init(&ps->pvconn, &pstream_pvconn_class, name_);
+ ps->pstream = pstream;
*pvconnp = &ps->pvconn;
return 0;
}
}
static void
- vlog_unixctl_set(struct unixctl_conn *conn, const char *args, void *aux UNUSED)
-vlog_unixctl_set(struct unixctl_conn *conn, const char *args)
++vlog_unixctl_set(struct unixctl_conn *conn,
++ const char *args, void *aux OVS_UNUSED)
{
char *msg = vlog_set_levels_from_string(args);
unixctl_command_reply(conn, msg ? 501 : 202, msg);
}
static void
-vlog_unixctl_list(struct unixctl_conn *conn, const char *args OVS_UNUSED)
+vlog_unixctl_list(struct unixctl_conn *conn,
- const char *args UNUSED, void *aux UNUSED)
++ const char *args OVS_UNUSED, void *aux OVS_UNUSED)
{
char *msg = vlog_get_levels();
unixctl_command_reply(conn, 200, msg);
}
static void
-vlog_unixctl_reopen(struct unixctl_conn *conn, const char *args OVS_UNUSED)
+vlog_unixctl_reopen(struct unixctl_conn *conn,
- const char *args UNUSED, void *aux UNUSED)
++ const char *args OVS_UNUSED, void *aux OVS_UNUSED)
{
if (log_file_name) {
int error = vlog_reopen_log_file();
--- /dev/null
- struct json *result UNUSED)
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <limits.h>
+
+#include "column.h"
+#include "condition.h"
+#include "file.h"
+#include "json.h"
+#include "mutation.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb.h"
+#include "query.h"
+#include "row.h"
+#include "table.h"
+#include "timeval.h"
+#include "transaction.h"
+
+struct ovsdb_execution {
+ struct ovsdb *db;
+ struct ovsdb_txn *txn;
+ struct ovsdb_symbol_table *symtab;
+ bool durable;
+
+ /* Triggers. */
+ long long int elapsed_msec;
+ long long int timeout_msec;
+};
+
+typedef struct ovsdb_error *ovsdb_operation_executor(struct ovsdb_execution *,
+ struct ovsdb_parser *,
+ struct json *result);
+
+static ovsdb_operation_executor ovsdb_execute_insert;
+static ovsdb_operation_executor ovsdb_execute_select;
+static ovsdb_operation_executor ovsdb_execute_update;
+static ovsdb_operation_executor ovsdb_execute_mutate;
+static ovsdb_operation_executor ovsdb_execute_delete;
+static ovsdb_operation_executor ovsdb_execute_wait;
+static ovsdb_operation_executor ovsdb_execute_commit;
+static ovsdb_operation_executor ovsdb_execute_abort;
+static ovsdb_operation_executor ovsdb_execute_comment;
+
+static ovsdb_operation_executor *
+lookup_executor(const char *name)
+{
+ struct ovsdb_operation {
+ const char *name;
+ ovsdb_operation_executor *executor;
+ };
+
+ static const struct ovsdb_operation operations[] = {
+ { "insert", ovsdb_execute_insert },
+ { "select", ovsdb_execute_select },
+ { "update", ovsdb_execute_update },
+ { "mutate", ovsdb_execute_mutate },
+ { "delete", ovsdb_execute_delete },
+ { "wait", ovsdb_execute_wait },
+ { "commit", ovsdb_execute_commit },
+ { "abort", ovsdb_execute_abort },
+ { "comment", ovsdb_execute_comment },
+ };
+
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(operations); i++) {
+ const struct ovsdb_operation *c = &operations[i];
+ if (!strcmp(c->name, name)) {
+ return c->executor;
+ }
+ }
+ return NULL;
+}
+
+struct json *
+ovsdb_execute(struct ovsdb *db, const struct json *params,
+ long long int elapsed_msec, long long int *timeout_msec)
+{
+ struct ovsdb_execution x;
+ struct ovsdb_error *error;
+ struct json *results;
+ size_t n_operations;
+ size_t i;
+
+ if (params->type != JSON_ARRAY
+ || !params->u.array.n
+ || params->u.array.elems[0]->type != JSON_STRING
+ || strcmp(params->u.array.elems[0]->u.string, db->schema->name)) {
+ struct ovsdb_error *error;
+
+ if (params->type != JSON_ARRAY) {
+ error = ovsdb_syntax_error(params, NULL, "array expected");
+ } else {
+ error = ovsdb_syntax_error(params, NULL, "database name expected "
+ "as first parameter");
+ }
+
+ results = ovsdb_error_to_json(error);
+ ovsdb_error_destroy(error);
+ return results;
+ }
+
+ x.db = db;
+ x.txn = ovsdb_txn_create(db);
+ x.symtab = ovsdb_symbol_table_create();
+ x.durable = false;
+ x.elapsed_msec = elapsed_msec;
+ x.timeout_msec = LLONG_MAX;
+ results = NULL;
+
+ results = json_array_create_empty();
+ n_operations = params->u.array.n - 1;
+ error = NULL;
+ for (i = 1; i <= n_operations; i++) {
+ struct json *operation = params->u.array.elems[i];
+ struct ovsdb_error *parse_error;
+ struct ovsdb_parser parser;
+ struct json *result;
+ const struct json *op;
+
+ /* Parse and execute operation. */
+ ovsdb_parser_init(&parser, operation,
+ "ovsdb operation %zu of %zu", i + 1, n_operations);
+ op = ovsdb_parser_member(&parser, "op", OP_ID);
+ result = json_object_create();
+ if (op) {
+ const char *op_name = json_string(op);
+ ovsdb_operation_executor *executor = lookup_executor(op_name);
+ if (executor) {
+ error = executor(&x, &parser, result);
+ } else {
+ ovsdb_parser_raise_error(&parser, "No operation \"%s\"",
+ op_name);
+ }
+ } else {
+ assert(ovsdb_parser_has_error(&parser));
+ }
+
+ /* A parse error overrides any other error.
+ * An error overrides any other result. */
+ parse_error = ovsdb_parser_finish(&parser);
+ if (parse_error) {
+ ovsdb_error_destroy(error);
+ error = parse_error;
+ }
+ if (error) {
+ json_destroy(result);
+ result = ovsdb_error_to_json(error);
+ }
+ if (error && !strcmp(ovsdb_error_get_tag(error), "not supported")
+ && timeout_msec) {
+ ovsdb_txn_abort(x.txn);
+ *timeout_msec = x.timeout_msec;
+
+ json_destroy(result);
+ json_destroy(results);
+ results = NULL;
+ goto exit;
+ }
+
+ /* Add result to array. */
+ json_array_add(results, result);
+ if (error) {
+ break;
+ }
+ }
+
+ if (!error) {
+ error = ovsdb_txn_commit(x.txn, x.durable);
+ if (error) {
+ json_array_add(results, ovsdb_error_to_json(error));
+ }
+ } else {
+ ovsdb_txn_abort(x.txn);
+ }
+
+ while (json_array(results)->n < n_operations) {
+ json_array_add(results, json_null_create());
+ }
+
+exit:
+ ovsdb_error_destroy(error);
+ ovsdb_symbol_table_destroy(x.symtab);
+
+ return results;
+}
+
+struct ovsdb_error *
+ovsdb_execute_commit(struct ovsdb_execution *x, struct ovsdb_parser *parser,
- ovsdb_execute_abort(struct ovsdb_execution *x UNUSED,
- struct ovsdb_parser *parser UNUSED,
- struct json *result UNUSED)
++ struct json *result OVS_UNUSED)
+{
+ const struct json *durable;
+
+ durable = ovsdb_parser_member(parser, "durable", OP_BOOLEAN);
+ if (durable && json_boolean(durable)) {
+ x->durable = true;
+ }
+ return NULL;
+}
+
+static struct ovsdb_error *
- struct json *result UNUSED)
++ovsdb_execute_abort(struct ovsdb_execution *x OVS_UNUSED,
++ struct ovsdb_parser *parser OVS_UNUSED,
++ struct json *result OVS_UNUSED)
+{
+ return ovsdb_error("aborted", "aborted by request");
+}
+
+static struct ovsdb_table *
+parse_table(struct ovsdb_execution *x,
+ struct ovsdb_parser *parser, const char *member)
+{
+ struct ovsdb_table *table;
+ const char *table_name;
+ const struct json *json;
+
+ json = ovsdb_parser_member(parser, member, OP_ID);
+ if (!json) {
+ return NULL;
+ }
+ table_name = json_string(json);
+
+ table = shash_find_data(&x->db->tables, table_name);
+ if (!table) {
+ ovsdb_parser_raise_error(parser, "No table named %s.", table_name);
+ }
+ return table;
+}
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+parse_row(struct ovsdb_parser *parser, const char *member,
+ const struct ovsdb_table *table,
+ struct ovsdb_symbol_table *symtab,
+ struct ovsdb_row **rowp, struct ovsdb_column_set *columns)
+{
+ struct ovsdb_error *error;
+ const struct json *json;
+ struct ovsdb_row *row;
+
+ *rowp = NULL;
+
+ if (!table) {
+ return OVSDB_BUG("null table");
+ }
+ json = ovsdb_parser_member(parser, member, OP_OBJECT);
+ if (!json) {
+ return OVSDB_BUG("null row member");
+ }
+
+ row = ovsdb_row_create(table);
+ error = ovsdb_row_from_json(row, json, symtab, columns);
+ if (error) {
+ ovsdb_row_destroy(row);
+ return error;
+ } else {
+ *rowp = row;
+ return NULL;
+ }
+}
+
+struct ovsdb_error *
+ovsdb_execute_insert(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+ struct json *result)
+{
+ struct ovsdb_table *table;
+ struct ovsdb_row *row = NULL;
+ const struct json *uuid_name;
+ struct ovsdb_error *error;
+ struct uuid row_uuid;
+
+ table = parse_table(x, parser, "table");
+ uuid_name = ovsdb_parser_member(parser, "uuid-name", OP_ID | OP_OPTIONAL);
+ error = ovsdb_parser_get_error(parser);
+
+ if (uuid_name) {
+ struct ovsdb_symbol *symbol;
+
+ symbol = ovsdb_symbol_table_insert(x->symtab, json_string(uuid_name));
+ if (symbol->used) {
+ return ovsdb_syntax_error(uuid_name, "duplicate uuid-name",
+ "This \"uuid-name\" appeared on an "
+ "earlier \"insert\" operation.");
+ }
+ row_uuid = symbol->uuid;
+ symbol->used = true;
+ } else {
+ uuid_generate(&row_uuid);
+ }
+
+ if (!error) {
+ error = parse_row(parser, "row", table, x->symtab, &row, NULL);
+ }
+ if (!error) {
+ /* Check constraints for columns not included in "row", in case the
+ * default values do not satisfy the constraints. We could check only
+ * the columns that have their default values by supplying an
+ * ovsdb_column_set to parse_row() above, but I suspect that this is
+ * cheaper. */
+ const struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &table->schema->columns) {
+ const struct ovsdb_column *column = node->data;
+ const struct ovsdb_datum *datum = &row->fields[column->index];
+
+ /* If there are 0 keys or pairs, there's nothing to check.
+ * If there is 1, it might be a default value.
+ * If there are more, it can't be a default value, so the value has
+ * already been checked. */
+ if (datum->n == 1) {
+ error = ovsdb_datum_check_constraints(datum, &column->type);
+ if (error) {
+ ovsdb_row_destroy(row);
+ break;
+ }
+ }
+ }
+ }
+ if (!error) {
+ *ovsdb_row_get_uuid_rw(row) = row_uuid;
+ ovsdb_txn_row_insert(x->txn, row);
+ json_object_put(result, "uuid",
+ ovsdb_datum_to_json(&row->fields[OVSDB_COL_UUID],
+ &ovsdb_type_uuid));
+ }
+ return error;
+}
+
+struct ovsdb_error *
+ovsdb_execute_select(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+ struct json *result)
+{
+ struct ovsdb_table *table;
+ const struct json *where, *columns_json, *sort_json;
+ struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+ struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+ struct ovsdb_column_set sort = OVSDB_COLUMN_SET_INITIALIZER;
+ struct ovsdb_error *error;
+
+ table = parse_table(x, parser, "table");
+ where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+ columns_json = ovsdb_parser_member(parser, "columns",
+ OP_ARRAY | OP_OPTIONAL);
+ sort_json = ovsdb_parser_member(parser, "sort", OP_ARRAY | OP_OPTIONAL);
+
+ error = ovsdb_parser_get_error(parser);
+ if (!error) {
+ error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+ &condition);
+ }
+ if (!error) {
+ error = ovsdb_column_set_from_json(columns_json, table, &columns);
+ }
+ if (!error) {
+ error = ovsdb_column_set_from_json(sort_json, table, &sort);
+ }
+ if (!error) {
+ struct ovsdb_row_set rows = OVSDB_ROW_SET_INITIALIZER;
+
+ ovsdb_query_distinct(table, &condition, &columns, &rows);
+ ovsdb_row_set_sort(&rows, &sort);
+ json_object_put(result, "rows",
+ ovsdb_row_set_to_json(&rows, &columns));
+
+ ovsdb_row_set_destroy(&rows);
+ }
+
+ ovsdb_column_set_destroy(&columns);
+ ovsdb_column_set_destroy(&sort);
+ ovsdb_condition_destroy(&condition);
+
+ return error;
+}
+
+struct update_row_cbdata {
+ size_t n_matches;
+ struct ovsdb_txn *txn;
+ const struct ovsdb_row *row;
+ const struct ovsdb_column_set *columns;
+};
+
+static bool
+update_row_cb(const struct ovsdb_row *row, void *ur_)
+{
+ struct update_row_cbdata *ur = ur_;
+
+ ur->n_matches++;
+ if (!ovsdb_row_equal_columns(row, ur->row, ur->columns)) {
+ ovsdb_row_update_columns(ovsdb_txn_row_modify(ur->txn, row),
+ ur->row, ur->columns);
+ }
+
+ return true;
+}
+
+struct ovsdb_error *
+ovsdb_execute_update(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+ struct json *result)
+{
+ struct ovsdb_table *table;
+ const struct json *where;
+ struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+ struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+ struct ovsdb_row *row = NULL;
+ struct update_row_cbdata ur;
+ struct ovsdb_error *error;
+
+ table = parse_table(x, parser, "table");
+ where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+ error = ovsdb_parser_get_error(parser);
+ if (!error) {
+ error = parse_row(parser, "row", table, x->symtab, &row, &columns);
+ }
+ if (!error) {
+ error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+ &condition);
+ }
+ if (!error) {
+ ur.n_matches = 0;
+ ur.txn = x->txn;
+ ur.row = row;
+ ur.columns = &columns;
+ ovsdb_query(table, &condition, update_row_cb, &ur);
+ json_object_put(result, "count", json_integer_create(ur.n_matches));
+ }
+
+ ovsdb_row_destroy(row);
+ ovsdb_column_set_destroy(&columns);
+ ovsdb_condition_destroy(&condition);
+
+ return error;
+}
+
+struct mutate_row_cbdata {
+ size_t n_matches;
+ struct ovsdb_txn *txn;
+ const struct ovsdb_mutation_set *mutations;
+};
+
+static bool
+mutate_row_cb(const struct ovsdb_row *row, void *mr_)
+{
+ struct mutate_row_cbdata *mr = mr_;
+
+ mr->n_matches++;
+ ovsdb_mutation_set_execute(ovsdb_txn_row_modify(mr->txn, row),
+ mr->mutations);
+
+ return true;
+}
+
+struct ovsdb_error *
+ovsdb_execute_mutate(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+ struct json *result)
+{
+ struct ovsdb_table *table;
+ const struct json *where;
+ const struct json *mutations_json;
+ struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+ struct ovsdb_mutation_set mutations = OVSDB_MUTATION_SET_INITIALIZER;
+ struct ovsdb_row *row = NULL;
+ struct mutate_row_cbdata mr;
+ struct ovsdb_error *error;
+
+ table = parse_table(x, parser, "table");
+ where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+ mutations_json = ovsdb_parser_member(parser, "mutations", OP_ARRAY);
+ error = ovsdb_parser_get_error(parser);
+ if (!error) {
+ error = ovsdb_mutation_set_from_json(table->schema, mutations_json,
+ x->symtab, &mutations);
+ }
+ if (!error) {
+ error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+ &condition);
+ }
+ if (!error) {
+ mr.n_matches = 0;
+ mr.txn = x->txn;
+ mr.mutations = &mutations;
+ ovsdb_query(table, &condition, mutate_row_cb, &mr);
+ json_object_put(result, "count", json_integer_create(mr.n_matches));
+ }
+
+ ovsdb_row_destroy(row);
+ ovsdb_mutation_set_destroy(&mutations);
+ ovsdb_condition_destroy(&condition);
+
+ return error;
+}
+
+struct delete_row_cbdata {
+ size_t n_matches;
+ const struct ovsdb_table *table;
+ struct ovsdb_txn *txn;
+};
+
+static bool
+delete_row_cb(const struct ovsdb_row *row, void *dr_)
+{
+ struct delete_row_cbdata *dr = dr_;
+
+ dr->n_matches++;
+ ovsdb_txn_row_delete(dr->txn, row);
+
+ return true;
+}
+
+struct ovsdb_error *
+ovsdb_execute_delete(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+ struct json *result)
+{
+ struct ovsdb_table *table;
+ const struct json *where;
+ struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+ struct ovsdb_error *error;
+
+ where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+ table = parse_table(x, parser, "table");
+ error = ovsdb_parser_get_error(parser);
+ if (!error) {
+ error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+ &condition);
+ }
+ if (!error) {
+ struct delete_row_cbdata dr;
+
+ dr.n_matches = 0;
+ dr.table = table;
+ dr.txn = x->txn;
+ ovsdb_query(table, &condition, delete_row_cb, &dr);
+
+ json_object_put(result, "count", json_integer_create(dr.n_matches));
+ }
+
+ ovsdb_condition_destroy(&condition);
+
+ return error;
+}
+
+struct wait_auxdata {
+ struct ovsdb_row_hash *actual;
+ struct ovsdb_row_hash *expected;
+ bool *equal;
+};
+
+static bool
+ovsdb_execute_wait_query_cb(const struct ovsdb_row *row, void *aux_)
+{
+ struct wait_auxdata *aux = aux_;
+
+ if (ovsdb_row_hash_contains(aux->expected, row)) {
+ ovsdb_row_hash_insert(aux->actual, row);
+ return true;
+ } else {
+ /* The query row isn't in the expected result set, so the actual and
+ * expected results sets definitely differ and we can short-circuit the
+ * rest of the query. */
+ *aux->equal = false;
+ return false;
+ }
+}
+
+static struct ovsdb_error *
+ovsdb_execute_wait(struct ovsdb_execution *x, struct ovsdb_parser *parser,
- struct json *result UNUSED)
++ struct json *result OVS_UNUSED)
+{
+ struct ovsdb_table *table;
+ const struct json *timeout, *where, *columns_json, *until, *rows;
+ struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+ struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+ struct ovsdb_row_hash expected = OVSDB_ROW_HASH_INITIALIZER(expected);
+ struct ovsdb_row_hash actual = OVSDB_ROW_HASH_INITIALIZER(actual);
+ struct ovsdb_error *error;
+ struct wait_auxdata aux;
+ long long int timeout_msec = 0;
+ size_t i;
+
+ timeout = ovsdb_parser_member(parser, "timeout", OP_NUMBER | OP_OPTIONAL);
+ where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+ columns_json = ovsdb_parser_member(parser, "columns",
+ OP_ARRAY | OP_OPTIONAL);
+ until = ovsdb_parser_member(parser, "until", OP_STRING);
+ rows = ovsdb_parser_member(parser, "rows", OP_ARRAY);
+ table = parse_table(x, parser, "table");
+ error = ovsdb_parser_get_error(parser);
+ if (!error) {
+ error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+ &condition);
+ }
+ if (!error) {
+ error = ovsdb_column_set_from_json(columns_json, table, &columns);
+ }
+ if (!error) {
+ if (timeout) {
+ timeout_msec = MIN(LLONG_MAX, json_real(timeout));
+ if (timeout_msec < 0) {
+ error = ovsdb_syntax_error(timeout, NULL,
+ "timeout must be nonnegative");
+ } else if (timeout_msec < x->timeout_msec) {
+ x->timeout_msec = timeout_msec;
+ }
+ } else {
+ timeout_msec = LLONG_MAX;
+ }
+ if (strcmp(json_string(until), "==")
+ && strcmp(json_string(until), "!=")) {
+ error = ovsdb_syntax_error(until, NULL,
+ "\"until\" must be \"==\" or \"!=\"");
+ }
+ }
+ if (!error) {
+ /* Parse "rows" into 'expected'. */
+ ovsdb_row_hash_init(&expected, &columns);
+ for (i = 0; i < rows->u.array.n; i++) {
+ struct ovsdb_error *error;
+ struct ovsdb_row *row;
+
+ row = ovsdb_row_create(table);
+ error = ovsdb_row_from_json(row, rows->u.array.elems[i], x->symtab,
+ NULL);
+ if (error) {
+ break;
+ }
+
+ if (!ovsdb_row_hash_insert(&expected, row)) {
+ /* XXX Perhaps we should abort with an error or log a
+ * warning. */
+ ovsdb_row_destroy(row);
+ }
+ }
+ }
+ if (!error) {
+ /* Execute query. */
+ bool equal = true;
+ ovsdb_row_hash_init(&actual, &columns);
+ aux.actual = &actual;
+ aux.expected = &expected;
+ aux.equal = &equal;
+ ovsdb_query(table, &condition, ovsdb_execute_wait_query_cb, &aux);
+ if (equal) {
+ /* We know that every row in 'actual' is also in 'expected'. We
+ * also know that all of the rows in 'actual' are distinct and that
+ * all of the rows in 'expected' are distinct. Therefore, if
+ * 'actual' and 'expected' have the same number of rows, then they
+ * have the same content. */
+ size_t n_actual = ovsdb_row_hash_count(&actual);
+ size_t n_expected = ovsdb_row_hash_count(&expected);
+ equal = n_actual == n_expected;
+ }
+ if (!strcmp(json_string(until), "==") != equal) {
+ if (timeout && x->elapsed_msec >= timeout_msec) {
+ if (x->elapsed_msec) {
+ error = ovsdb_error("timed out",
+ "\"wait\" timed out after %lld ms",
+ x->elapsed_msec);
+ } else {
+ error = ovsdb_error("timed out", "\"wait\" timed out");
+ }
+ } else {
+ /* ovsdb_execute() will change this, if triggers really are
+ * supported. */
+ error = ovsdb_error("not supported", "triggers not supported");
+ }
+ }
+ }
+
+
+ ovsdb_row_hash_destroy(&expected, true);
+ ovsdb_row_hash_destroy(&actual, false);
+ ovsdb_column_set_destroy(&columns);
+ ovsdb_condition_destroy(&condition);
+
+ return error;
+}
+
+static struct ovsdb_error *
+ovsdb_execute_comment(struct ovsdb_execution *x, struct ovsdb_parser *parser,
++ struct json *result OVS_UNUSED)
+{
+ const struct json *comment;
+
+ comment = ovsdb_parser_member(parser, "comment", OP_STRING);
+ if (!comment) {
+ return NULL;
+ }
+ ovsdb_txn_add_comment(x->txn, json_string(comment));
+
+ return NULL;
+}
--- /dev/null
- const struct ovsdb_txn *txn, bool durable UNUSED)
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "jsonrpc-server.h"
+
+#include <assert.h>
+#include <errno.h>
+
+#include "column.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb.h"
+#include "reconnect.h"
+#include "row.h"
+#include "stream.h"
+#include "table.h"
+#include "timeval.h"
+#include "transaction.h"
+#include "trigger.h"
+
+#define THIS_MODULE VLM_ovsdb_jsonrpc_server
+#include "vlog.h"
+
+struct ovsdb_jsonrpc_remote;
+struct ovsdb_jsonrpc_session;
+
+/* Message rate-limiting. */
+struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+/* Sessions. */
+static struct ovsdb_jsonrpc_session *ovsdb_jsonrpc_session_create(
+ struct ovsdb_jsonrpc_remote *, struct jsonrpc_session *);
+static void ovsdb_jsonrpc_session_run_all(struct ovsdb_jsonrpc_remote *);
+static void ovsdb_jsonrpc_session_wait_all(struct ovsdb_jsonrpc_remote *);
+static void ovsdb_jsonrpc_session_close_all(struct ovsdb_jsonrpc_remote *);
+
+/* Triggers. */
+static void ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *,
+ struct json *id, struct json *params);
+static struct ovsdb_jsonrpc_trigger *ovsdb_jsonrpc_trigger_find(
+ struct ovsdb_jsonrpc_session *, const struct json *id, size_t hash);
+static void ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *);
+static void ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *);
+static void ovsdb_jsonrpc_trigger_complete_done(
+ struct ovsdb_jsonrpc_session *);
+
+/* Monitors. */
+static struct json *ovsdb_jsonrpc_monitor_create(
+ struct ovsdb_jsonrpc_session *, struct json *params);
+static struct jsonrpc_msg *ovsdb_jsonrpc_monitor_cancel(
+ struct ovsdb_jsonrpc_session *,
+ struct json_array *params,
+ const struct json *request_id);
+static void ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *);
+\f
+/* JSON-RPC database server. */
+
+struct ovsdb_jsonrpc_server {
+ struct ovsdb *db;
+ unsigned int n_sessions, max_sessions;
+ struct shash remotes; /* Contains "struct ovsdb_jsonrpc_remote *"s. */
+};
+
+/* A configured remote. This is either a passive stream listener plus a list
+ * of the currently connected sessions, or a list of exactly one active
+ * session. */
+struct ovsdb_jsonrpc_remote {
+ struct ovsdb_jsonrpc_server *server;
+ struct pstream *listener; /* Listener, if passive. */
+ struct list sessions; /* List of "struct ovsdb_jsonrpc_session"s. */
+};
+
+static void ovsdb_jsonrpc_server_add_remote(struct ovsdb_jsonrpc_server *,
+ const char *name);
+static void ovsdb_jsonrpc_server_del_remote(struct shash_node *);
+
+struct ovsdb_jsonrpc_server *
+ovsdb_jsonrpc_server_create(struct ovsdb *db)
+{
+ struct ovsdb_jsonrpc_server *server = xzalloc(sizeof *server);
+ server->db = db;
+ server->max_sessions = 64;
+ shash_init(&server->remotes);
+ return server;
+}
+
+void
+ovsdb_jsonrpc_server_destroy(struct ovsdb_jsonrpc_server *svr)
+{
+ struct shash_node *node, *next;
+
+ SHASH_FOR_EACH_SAFE (node, next, &svr->remotes) {
+ ovsdb_jsonrpc_server_del_remote(node);
+ }
+ shash_destroy(&svr->remotes);
+ free(svr);
+}
+
+/* Sets 'svr''s current set of remotes to the names in 'new_remotes'. The data
+ * values in 'new_remotes' are ignored.
+ *
+ * A remote is an active or passive stream connection method, e.g. "pssl:" or
+ * "tcp:1.2.3.4". */
+void
+ovsdb_jsonrpc_server_set_remotes(struct ovsdb_jsonrpc_server *svr,
+ const struct shash *new_remotes)
+{
+ struct shash_node *node, *next;
+
+ SHASH_FOR_EACH_SAFE (node, next, &svr->remotes) {
+ if (!shash_find(new_remotes, node->name)) {
+ ovsdb_jsonrpc_server_del_remote(node);
+ }
+ }
+ SHASH_FOR_EACH (node, new_remotes) {
+ if (!shash_find(&svr->remotes, node->name)) {
+ ovsdb_jsonrpc_server_add_remote(svr, node->name);
+ }
+ }
+}
+
+static void
+ovsdb_jsonrpc_server_add_remote(struct ovsdb_jsonrpc_server *svr,
+ const char *name)
+{
+ struct ovsdb_jsonrpc_remote *remote;
+ struct pstream *listener;
+ int error;
+
+ error = pstream_open(name, &listener);
+ if (error && error != EAFNOSUPPORT) {
+ VLOG_ERR_RL(&rl, "%s: listen failed: %s", name, strerror(error));
+ return;
+ }
+
+ remote = xmalloc(sizeof *remote);
+ remote->server = svr;
+ remote->listener = listener;
+ list_init(&remote->sessions);
+ shash_add(&svr->remotes, name, remote);
+
+ if (!listener) {
+ ovsdb_jsonrpc_session_create(remote, jsonrpc_session_open(name));
+ }
+}
+
+static void
+ovsdb_jsonrpc_server_del_remote(struct shash_node *node)
+{
+ struct ovsdb_jsonrpc_remote *remote = node->data;
+
+ ovsdb_jsonrpc_session_close_all(remote);
+ pstream_close(remote->listener);
+ shash_delete(&remote->server->remotes, node);
+ free(remote);
+}
+
+void
+ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *svr)
+{
+ struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &svr->remotes) {
+ struct ovsdb_jsonrpc_remote *remote = node->data;
+
+ if (remote->listener && svr->n_sessions < svr->max_sessions) {
+ struct stream *stream;
+ int error;
+
+ error = pstream_accept(remote->listener, &stream);
+ if (!error) {
+ struct jsonrpc_session *js;
+ js = jsonrpc_session_open_unreliably(jsonrpc_open(stream));
+ ovsdb_jsonrpc_session_create(remote, js);
+ } else if (error != EAGAIN) {
+ VLOG_WARN_RL(&rl, "%s: accept failed: %s",
+ pstream_get_name(remote->listener),
+ strerror(error));
+ }
+ }
+
+ ovsdb_jsonrpc_session_run_all(remote);
+ }
+}
+
+void
+ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *svr)
+{
+ struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &svr->remotes) {
+ struct ovsdb_jsonrpc_remote *remote = node->data;
+
+ if (remote->listener && svr->n_sessions < svr->max_sessions) {
+ pstream_wait(remote->listener);
+ }
+
+ ovsdb_jsonrpc_session_wait_all(remote);
+ }
+}
+\f
+/* JSON-RPC database server session. */
+
+struct ovsdb_jsonrpc_session {
+ struct ovsdb_jsonrpc_remote *remote;
+ struct list node; /* Element in remote's sessions list. */
+
+ /* Triggers. */
+ struct hmap triggers; /* Hmap of "struct ovsdb_jsonrpc_trigger"s. */
+ struct list completions; /* Completed triggers. */
+
+ /* Monitors. */
+ struct hmap monitors; /* Hmap of "struct ovsdb_jsonrpc_monitor"s. */
+
+ /* Network connectivity. */
+ struct jsonrpc_session *js; /* JSON-RPC session. */
+ unsigned int js_seqno; /* Last jsonrpc_session_get_seqno() value. */
+};
+
+static void ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *);
+static int ovsdb_jsonrpc_session_run(struct ovsdb_jsonrpc_session *);
+static void ovsdb_jsonrpc_session_wait(struct ovsdb_jsonrpc_session *);
+static void ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *,
+ struct jsonrpc_msg *);
+static void ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *,
+ struct jsonrpc_msg *);
+
+static struct ovsdb_jsonrpc_session *
+ovsdb_jsonrpc_session_create(struct ovsdb_jsonrpc_remote *remote,
+ struct jsonrpc_session *js)
+{
+ struct ovsdb_jsonrpc_session *s;
+
+ s = xzalloc(sizeof *s);
+ s->remote = remote;
+ list_push_back(&remote->sessions, &s->node);
+ hmap_init(&s->triggers);
+ hmap_init(&s->monitors);
+ list_init(&s->completions);
+ s->js = js;
+ s->js_seqno = jsonrpc_session_get_seqno(js);
+
+ remote->server->n_sessions++;
+
+ return s;
+}
+
+static void
+ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *s)
+{
+ ovsdb_jsonrpc_monitor_remove_all(s);
+ jsonrpc_session_close(s->js);
+ list_remove(&s->node);
+ s->remote->server->n_sessions--;
+ free(s);
+}
+
+static int
+ovsdb_jsonrpc_session_run(struct ovsdb_jsonrpc_session *s)
+{
+ jsonrpc_session_run(s->js);
+ if (s->js_seqno != jsonrpc_session_get_seqno(s->js)) {
+ s->js_seqno = jsonrpc_session_get_seqno(s->js);
+ ovsdb_jsonrpc_trigger_complete_all(s);
+ ovsdb_jsonrpc_monitor_remove_all(s);
+ }
+
+ ovsdb_jsonrpc_trigger_complete_done(s);
+
+ if (!jsonrpc_session_get_backlog(s->js)) {
+ struct jsonrpc_msg *msg = jsonrpc_session_recv(s->js);
+ if (msg) {
+ if (msg->type == JSONRPC_REQUEST) {
+ ovsdb_jsonrpc_session_got_request(s, msg);
+ } else if (msg->type == JSONRPC_NOTIFY) {
+ ovsdb_jsonrpc_session_got_notify(s, msg);
+ } else {
+ VLOG_WARN("%s: received unexpected %s message",
+ jsonrpc_session_get_name(s->js),
+ jsonrpc_msg_type_to_string(msg->type));
+ jsonrpc_session_force_reconnect(s->js);
+ jsonrpc_msg_destroy(msg);
+ }
+ }
+ }
+ return jsonrpc_session_is_alive(s->js) ? 0 : ETIMEDOUT;
+}
+
+static void
+ovsdb_jsonrpc_session_run_all(struct ovsdb_jsonrpc_remote *remote)
+{
+ struct ovsdb_jsonrpc_session *s, *next;
+
+ LIST_FOR_EACH_SAFE (s, next, struct ovsdb_jsonrpc_session, node,
+ &remote->sessions) {
+ int error = ovsdb_jsonrpc_session_run(s);
+ if (error) {
+ ovsdb_jsonrpc_session_close(s);
+ }
+ }
+}
+
+static void
+ovsdb_jsonrpc_session_wait(struct ovsdb_jsonrpc_session *s)
+{
+ jsonrpc_session_wait(s->js);
+ if (!jsonrpc_session_get_backlog(s->js)) {
+ jsonrpc_session_recv_wait(s->js);
+ }
+}
+
+static void
+ovsdb_jsonrpc_session_wait_all(struct ovsdb_jsonrpc_remote *remote)
+{
+ struct ovsdb_jsonrpc_session *s;
+
+ LIST_FOR_EACH (s, struct ovsdb_jsonrpc_session, node, &remote->sessions) {
+ ovsdb_jsonrpc_session_wait(s);
+ }
+}
+
+static void
+ovsdb_jsonrpc_session_close_all(struct ovsdb_jsonrpc_remote *remote)
+{
+ struct ovsdb_jsonrpc_session *s, *next;
+
+ LIST_FOR_EACH_SAFE (s, next, struct ovsdb_jsonrpc_session, node,
+ &remote->sessions) {
+ ovsdb_jsonrpc_session_close(s);
+ }
+}
+
+static const char *
+get_db_name(const struct ovsdb_jsonrpc_session *s)
+{
+ return s->remote->server->db->schema->name;
+}
+
+static struct jsonrpc_msg *
+ovsdb_jsonrpc_check_db_name(const struct ovsdb_jsonrpc_session *s,
+ const struct jsonrpc_msg *request)
+{
+ struct json_array *params;
+ const char *want_db_name;
+ const char *have_db_name;
+ struct ovsdb_error *error;
+ struct jsonrpc_msg *reply;
+
+ params = json_array(request->params);
+ if (!params->n || params->elems[0]->type != JSON_STRING) {
+ error = ovsdb_syntax_error(
+ request->params, NULL,
+ "%s request params must begin with <db-name>", request->method);
+ goto error;
+ }
+
+ want_db_name = params->elems[0]->u.string;
+ have_db_name = get_db_name(s);
+ if (strcmp(want_db_name, have_db_name)) {
+ error = ovsdb_syntax_error(
+ request->params, "unknown database",
+ "%s request specifies unknown database %s",
+ request->method, want_db_name);
+ goto error;
+ }
+
+ return NULL;
+
+error:
+ reply = jsonrpc_create_reply(ovsdb_error_to_json(error), request->id);
+ ovsdb_error_destroy(error);
+ return reply;
+}
+
+static struct jsonrpc_msg *
+execute_transaction(struct ovsdb_jsonrpc_session *s,
+ struct jsonrpc_msg *request)
+{
+ ovsdb_jsonrpc_trigger_create(s, request->id, request->params);
+ request->id = NULL;
+ request->params = NULL;
+ jsonrpc_msg_destroy(request);
+ return NULL;
+}
+
+static void
+ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *s,
+ struct jsonrpc_msg *request)
+{
+ struct jsonrpc_msg *reply;
+
+ if (!strcmp(request->method, "transact")) {
+ reply = ovsdb_jsonrpc_check_db_name(s, request);
+ if (!reply) {
+ reply = execute_transaction(s, request);
+ }
+ } else if (!strcmp(request->method, "monitor")) {
+ reply = ovsdb_jsonrpc_check_db_name(s, request);
+ if (!reply) {
+ reply = jsonrpc_create_reply(
+ ovsdb_jsonrpc_monitor_create(s, request->params), request->id);
+ }
+ } else if (!strcmp(request->method, "monitor_cancel")) {
+ reply = ovsdb_jsonrpc_monitor_cancel(s, json_array(request->params),
+ request->id);
+ } else if (!strcmp(request->method, "get_schema")) {
+ reply = ovsdb_jsonrpc_check_db_name(s, request);
+ if (!reply) {
+ reply = jsonrpc_create_reply(
+ ovsdb_schema_to_json(s->remote->server->db->schema),
+ request->id);
+ }
+ } else if (!strcmp(request->method, "list_dbs")) {
+ reply = jsonrpc_create_reply(
+ json_array_create_1(json_string_create(get_db_name(s))),
+ request->id);
+ } else if (!strcmp(request->method, "echo")) {
+ reply = jsonrpc_create_reply(json_clone(request->params), request->id);
+ } else {
+ reply = jsonrpc_create_error(json_string_create("unknown method"),
+ request->id);
+ }
+
+ if (reply) {
+ jsonrpc_msg_destroy(request);
+ jsonrpc_session_send(s->js, reply);
+ }
+}
+
+static void
+execute_cancel(struct ovsdb_jsonrpc_session *s, struct jsonrpc_msg *request)
+{
+ if (json_array(request->params)->n == 1) {
+ struct ovsdb_jsonrpc_trigger *t;
+ struct json *id;
+
+ id = request->params->u.array.elems[0];
+ t = ovsdb_jsonrpc_trigger_find(s, id, json_hash(id, 0));
+ if (t) {
+ ovsdb_jsonrpc_trigger_complete(t);
+ }
+ }
+}
+
+static void
+ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *s,
+ struct jsonrpc_msg *request)
+{
+ if (!strcmp(request->method, "cancel")) {
+ execute_cancel(s, request);
+ }
+ jsonrpc_msg_destroy(request);
+}
+\f
+/* JSON-RPC database server triggers.
+ *
+ * (Every transaction is treated as a trigger even if it doesn't actually have
+ * any "wait" operations.) */
+
+struct ovsdb_jsonrpc_trigger {
+ struct ovsdb_trigger trigger;
+ struct ovsdb_jsonrpc_session *session;
+ struct hmap_node hmap_node; /* In session's "triggers" hmap. */
+ struct json *id;
+};
+
+static void
+ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *s,
+ struct json *id, struct json *params)
+{
+ struct ovsdb_jsonrpc_trigger *t;
+ size_t hash;
+
+ /* Check for duplicate ID. */
+ hash = json_hash(id, 0);
+ t = ovsdb_jsonrpc_trigger_find(s, id, hash);
+ if (t) {
+ struct jsonrpc_msg *msg;
+
+ msg = jsonrpc_create_error(json_string_create("duplicate request ID"),
+ id);
+ jsonrpc_session_send(s->js, msg);
+ json_destroy(id);
+ json_destroy(params);
+ return;
+ }
+
+ /* Insert into trigger table. */
+ t = xmalloc(sizeof *t);
+ ovsdb_trigger_init(s->remote->server->db,
+ &t->trigger, params, &s->completions,
+ time_msec());
+ t->session = s;
+ t->id = id;
+ hmap_insert(&s->triggers, &t->hmap_node, hash);
+
+ /* Complete early if possible. */
+ if (ovsdb_trigger_is_complete(&t->trigger)) {
+ ovsdb_jsonrpc_trigger_complete(t);
+ }
+}
+
+static struct ovsdb_jsonrpc_trigger *
+ovsdb_jsonrpc_trigger_find(struct ovsdb_jsonrpc_session *s,
+ const struct json *id, size_t hash)
+{
+ struct ovsdb_jsonrpc_trigger *t;
+
+ HMAP_FOR_EACH_WITH_HASH (t, struct ovsdb_jsonrpc_trigger, hmap_node, hash,
+ &s->triggers) {
+ if (json_equal(t->id, id)) {
+ return t;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *t)
+{
+ struct ovsdb_jsonrpc_session *s = t->session;
+
+ if (jsonrpc_session_is_connected(s->js)) {
+ struct jsonrpc_msg *reply;
+ struct json *result;
+
+ result = ovsdb_trigger_steal_result(&t->trigger);
+ if (result) {
+ reply = jsonrpc_create_reply(result, t->id);
+ } else {
+ reply = jsonrpc_create_error(json_string_create("canceled"),
+ t->id);
+ }
+ jsonrpc_session_send(s->js, reply);
+ }
+
+ json_destroy(t->id);
+ ovsdb_trigger_destroy(&t->trigger);
+ hmap_remove(&s->triggers, &t->hmap_node);
+ free(t);
+}
+
+static void
+ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *s)
+{
+ struct ovsdb_jsonrpc_trigger *t, *next;
+ HMAP_FOR_EACH_SAFE (t, next, struct ovsdb_jsonrpc_trigger, hmap_node,
+ &s->triggers) {
+ ovsdb_jsonrpc_trigger_complete(t);
+ }
+}
+
+static void
+ovsdb_jsonrpc_trigger_complete_done(struct ovsdb_jsonrpc_session *s)
+{
+ while (!list_is_empty(&s->completions)) {
+ struct ovsdb_jsonrpc_trigger *t
+ = CONTAINER_OF(s->completions.next,
+ struct ovsdb_jsonrpc_trigger, trigger.node);
+ ovsdb_jsonrpc_trigger_complete(t);
+ }
+}
+\f
+/* JSON-RPC database table monitors. */
+
+enum ovsdb_jsonrpc_monitor_selection {
+ OJMS_INITIAL = 1 << 0, /* All rows when monitor is created. */
+ OJMS_INSERT = 1 << 1, /* New rows. */
+ OJMS_DELETE = 1 << 2, /* Deleted rows. */
+ OJMS_MODIFY = 1 << 3 /* Modified rows. */
+};
+
+struct ovsdb_jsonrpc_monitor_table {
+ const struct ovsdb_table *table;
+ enum ovsdb_jsonrpc_monitor_selection select;
+ struct ovsdb_column_set columns;
+};
+
+struct ovsdb_jsonrpc_monitor {
+ struct ovsdb_replica replica;
+ struct ovsdb_jsonrpc_session *session;
+ struct hmap_node node; /* In ovsdb_jsonrpc_session's "monitors". */
+
+ struct json *monitor_id;
+ struct shash tables; /* Holds "struct ovsdb_jsonrpc_monitor_table"s. */
+};
+
+static const struct ovsdb_replica_class ovsdb_jsonrpc_replica_class;
+
+struct ovsdb_jsonrpc_monitor *ovsdb_jsonrpc_monitor_find(
+ struct ovsdb_jsonrpc_session *, const struct json *monitor_id);
+static void ovsdb_jsonrpc_monitor_destroy(struct ovsdb_replica *);
+static struct json *ovsdb_jsonrpc_monitor_get_initial(
+ const struct ovsdb_jsonrpc_monitor *);
+
+static bool
+parse_bool(struct ovsdb_parser *parser, const char *name, bool default_value)
+{
+ const struct json *json;
+
+ json = ovsdb_parser_member(parser, name, OP_BOOLEAN | OP_OPTIONAL);
+ return json ? json_boolean(json) : default_value;
+}
+
+struct ovsdb_jsonrpc_monitor *
+ovsdb_jsonrpc_monitor_find(struct ovsdb_jsonrpc_session *s,
+ const struct json *monitor_id)
+{
+ struct ovsdb_jsonrpc_monitor *m;
+
+ HMAP_FOR_EACH_WITH_HASH (m, struct ovsdb_jsonrpc_monitor, node,
+ json_hash(monitor_id, 0), &s->monitors) {
+ if (json_equal(m->monitor_id, monitor_id)) {
+ return m;
+ }
+ }
+
+ return NULL;
+}
+
+static struct json *
+ovsdb_jsonrpc_monitor_create(struct ovsdb_jsonrpc_session *s,
+ struct json *params)
+{
+ struct ovsdb_jsonrpc_monitor *m = NULL;
+ struct json *monitor_id, *monitor_requests;
+ struct ovsdb_error *error = NULL;
+ struct shash_node *node;
+ struct json *json;
+
+ if (json_array(params)->n != 3) {
+ error = ovsdb_syntax_error(params, NULL, "invalid parameters");
+ goto error;
+ }
+ monitor_id = params->u.array.elems[1];
+ monitor_requests = params->u.array.elems[2];
+ if (monitor_requests->type != JSON_OBJECT) {
+ error = ovsdb_syntax_error(monitor_requests, NULL,
+ "monitor-requests must be object");
+ goto error;
+ }
+
+ if (ovsdb_jsonrpc_monitor_find(s, monitor_id)) {
+ error = ovsdb_syntax_error(monitor_id, NULL, "duplicate monitor ID");
+ goto error;
+ }
+
+ m = xzalloc(sizeof *m);
+ ovsdb_replica_init(&m->replica, &ovsdb_jsonrpc_replica_class);
+ ovsdb_add_replica(s->remote->server->db, &m->replica);
+ m->session = s;
+ hmap_insert(&s->monitors, &m->node, json_hash(monitor_id, 0));
+ m->monitor_id = json_clone(monitor_id);
+ shash_init(&m->tables);
+
+ SHASH_FOR_EACH (node, json_object(monitor_requests)) {
+ const struct ovsdb_table *table;
+ struct ovsdb_jsonrpc_monitor_table *mt;
+ const struct json *columns_json, *select_json;
+ struct ovsdb_parser parser;
+
+ table = ovsdb_get_table(s->remote->server->db, node->name);
+ if (!table) {
+ error = ovsdb_syntax_error(NULL, NULL,
+ "no table named %s", node->name);
+ goto error;
+ }
+
+ mt = xzalloc(sizeof *mt);
+ mt->table = table;
+ mt->select = OJMS_INITIAL | OJMS_INSERT | OJMS_DELETE | OJMS_MODIFY;
+ ovsdb_column_set_init(&mt->columns);
+ shash_add(&m->tables, table->schema->name, mt);
+
+ ovsdb_parser_init(&parser, node->data, "table %s", node->name);
+ columns_json = ovsdb_parser_member(&parser, "columns",
+ OP_ARRAY | OP_OPTIONAL);
+ select_json = ovsdb_parser_member(&parser, "select",
+ OP_OBJECT | OP_OPTIONAL);
+ error = ovsdb_parser_finish(&parser);
+ if (error) {
+ goto error;
+ }
+
+ if (columns_json) {
+ error = ovsdb_column_set_from_json(columns_json, table,
+ &mt->columns);
+ if (error) {
+ goto error;
+ }
+ } else {
+ struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &table->schema->columns) {
+ const struct ovsdb_column *column = node->data;
+ if (column->index != OVSDB_COL_UUID) {
+ ovsdb_column_set_add(&mt->columns, column);
+ }
+ }
+ }
+
+ if (select_json) {
+ mt->select = 0;
+ ovsdb_parser_init(&parser, select_json, "table %s select",
+ table->schema->name);
+ if (parse_bool(&parser, "initial", true)) {
+ mt->select |= OJMS_INITIAL;
+ }
+ if (parse_bool(&parser, "insert", true)) {
+ mt->select |= OJMS_INSERT;
+ }
+ if (parse_bool(&parser, "delete", true)) {
+ mt->select |= OJMS_DELETE;
+ }
+ if (parse_bool(&parser, "modify", true)) {
+ mt->select |= OJMS_MODIFY;
+ }
+ error = ovsdb_parser_finish(&parser);
+ if (error) {
+ goto error;
+ }
+ }
+ }
+
+ return ovsdb_jsonrpc_monitor_get_initial(m);
+
+error:
+ if (m) {
+ ovsdb_remove_replica(s->remote->server->db, &m->replica);
+ }
+
+ json = ovsdb_error_to_json(error);
+ ovsdb_error_destroy(error);
+ return json;
+}
+
+static struct jsonrpc_msg *
+ovsdb_jsonrpc_monitor_cancel(struct ovsdb_jsonrpc_session *s,
+ struct json_array *params,
+ const struct json *request_id)
+{
+ if (params->n != 1) {
+ return jsonrpc_create_error(json_string_create("invalid parameters"),
+ request_id);
+ } else {
+ struct ovsdb_jsonrpc_monitor *m;
+
+ m = ovsdb_jsonrpc_monitor_find(s, params->elems[0]);
+ if (!m) {
+ return jsonrpc_create_error(json_string_create("unknown monitor"),
+ request_id);
+ } else {
+ ovsdb_remove_replica(s->remote->server->db, &m->replica);
+ return jsonrpc_create_reply(json_object_create(), request_id);
+ }
+ }
+}
+
+static void
+ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *s)
+{
+ struct ovsdb_jsonrpc_monitor *m, *next;
+
+ HMAP_FOR_EACH_SAFE (m, next,
+ struct ovsdb_jsonrpc_monitor, node, &s->monitors) {
+ ovsdb_remove_replica(s->remote->server->db, &m->replica);
+ }
+}
+
+static struct ovsdb_jsonrpc_monitor *
+ovsdb_jsonrpc_monitor_cast(struct ovsdb_replica *replica)
+{
+ assert(replica->class == &ovsdb_jsonrpc_replica_class);
+ return CONTAINER_OF(replica, struct ovsdb_jsonrpc_monitor, replica);
+}
+
+struct ovsdb_jsonrpc_monitor_aux {
+ bool initial; /* Sending initial contents of table? */
+ const struct ovsdb_jsonrpc_monitor *monitor;
+ struct json *json; /* JSON for the whole transaction. */
+
+ /* Current table. */
+ struct ovsdb_jsonrpc_monitor_table *mt;
+ struct json *table_json; /* JSON for table's transaction. */
+};
+
+static bool
+ovsdb_jsonrpc_monitor_change_cb(const struct ovsdb_row *old,
+ const struct ovsdb_row *new,
+ void *aux_)
+{
+ struct ovsdb_jsonrpc_monitor_aux *aux = aux_;
+ const struct ovsdb_jsonrpc_monitor *m = aux->monitor;
+ struct ovsdb_table *table = new ? new->table : old->table;
+ enum ovsdb_jsonrpc_monitor_selection type;
+ struct json *old_json, *new_json;
+ struct json *row_json;
+ char uuid[UUID_LEN + 1];
+ int n_changed;
+ size_t i;
+
+ if (!aux->mt || table != aux->mt->table) {
+ aux->mt = shash_find_data(&m->tables, table->schema->name);
+ aux->table_json = NULL;
+ if (!aux->mt) {
+ /* We don't care about rows in this table at all. Tell the caller
+ * to skip it. */
+ return false;
+ }
+ }
+
+ type = (aux->initial ? OJMS_INITIAL
+ : !old ? OJMS_INSERT
+ : !new ? OJMS_DELETE
+ : OJMS_MODIFY);
+ if (!(aux->mt->select & type)) {
+ /* We don't care about this type of change (but do want to be called
+ * back for changes to other rows in the same table). */
+ return true;
+ }
+
+ old_json = new_json = NULL;
+ n_changed = 0;
+ for (i = 0; i < aux->mt->columns.n_columns; i++) {
+ const struct ovsdb_column *column = aux->mt->columns.columns[i];
+ unsigned int idx = column->index;
+ bool changed = false;
+
+ if (type == OJMS_MODIFY) {
+ changed = !ovsdb_datum_equals(&old->fields[idx],
+ &new->fields[idx], &column->type);
+ n_changed += changed;
+ }
+ if (changed || type == OJMS_DELETE) {
+ if (!old_json) {
+ old_json = json_object_create();
+ }
+ json_object_put(old_json, column->name,
+ ovsdb_datum_to_json(&old->fields[idx],
+ &column->type));
+ }
+ if (type & (OJMS_INITIAL | OJMS_INSERT | OJMS_MODIFY)) {
+ if (!new_json) {
+ new_json = json_object_create();
+ }
+ json_object_put(new_json, column->name,
+ ovsdb_datum_to_json(&new->fields[idx],
+ &column->type));
+ }
+ }
+ if ((type == OJMS_MODIFY && !n_changed) || (!old_json && !new_json)) {
+ /* No reportable changes. */
+ json_destroy(old_json);
+ json_destroy(new_json);
+ return true;
+ }
+
+ /* Create JSON object for transaction overall. */
+ if (!aux->json) {
+ aux->json = json_object_create();
+ }
+
+ /* Create JSON object for transaction on this table. */
+ if (!aux->table_json) {
+ aux->table_json = json_object_create();
+ json_object_put(aux->json, aux->mt->table->schema->name,
+ aux->table_json);
+ }
+
+ /* Create JSON object for transaction on this row. */
+ row_json = json_object_create();
+ if (old_json) {
+ json_object_put(row_json, "old", old_json);
+ }
+ if (new_json) {
+ json_object_put(row_json, "new", new_json);
+ }
+
+ /* Add JSON row to JSON table. */
+ snprintf(uuid, sizeof uuid,
+ UUID_FMT, UUID_ARGS(ovsdb_row_get_uuid(new ? new : old)));
+ json_object_put(aux->table_json, uuid, row_json);
+
+ return true;
+}
+
+static void
+ovsdb_jsonrpc_monitor_init_aux(struct ovsdb_jsonrpc_monitor_aux *aux,
+ const struct ovsdb_jsonrpc_monitor *m,
+ bool initial)
+{
+ aux->initial = initial;
+ aux->monitor = m;
+ aux->json = NULL;
+ aux->mt = NULL;
+ aux->table_json = NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_jsonrpc_monitor_commit(struct ovsdb_replica *replica,
++ const struct ovsdb_txn *txn,
++ bool durable OVS_UNUSED)
+{
+ struct ovsdb_jsonrpc_monitor *m = ovsdb_jsonrpc_monitor_cast(replica);
+ struct ovsdb_jsonrpc_monitor_aux aux;
+
+ ovsdb_jsonrpc_monitor_init_aux(&aux, m, false);
+ ovsdb_txn_for_each_change(txn, ovsdb_jsonrpc_monitor_change_cb, &aux);
+ if (aux.json) {
+ struct jsonrpc_msg *msg;
+ struct json *params;
+
+ params = json_array_create_2(json_clone(aux.monitor->monitor_id),
+ aux.json);
+ msg = jsonrpc_create_notify("update", params);
+ jsonrpc_session_send(aux.monitor->session->js, msg);
+ }
+
+ return NULL;
+}
+
+static struct json *
+ovsdb_jsonrpc_monitor_get_initial(const struct ovsdb_jsonrpc_monitor *m)
+{
+ struct ovsdb_jsonrpc_monitor_aux aux;
+ struct shash_node *node;
+
+ ovsdb_jsonrpc_monitor_init_aux(&aux, m, true);
+ SHASH_FOR_EACH (node, &m->tables) {
+ struct ovsdb_jsonrpc_monitor_table *mt = node->data;
+
+ if (mt->select & OJMS_INITIAL) {
+ struct ovsdb_row *row;
+
+ HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node,
+ &mt->table->rows) {
+ ovsdb_jsonrpc_monitor_change_cb(NULL, row, &aux);
+ }
+ }
+ }
+ return aux.json ? aux.json : json_object_create();
+}
+
+static void
+ovsdb_jsonrpc_monitor_destroy(struct ovsdb_replica *replica)
+{
+ struct ovsdb_jsonrpc_monitor *m = ovsdb_jsonrpc_monitor_cast(replica);
+ struct shash_node *node;
+
+ json_destroy(m->monitor_id);
+ SHASH_FOR_EACH (node, &m->tables) {
+ struct ovsdb_jsonrpc_monitor_table *mt = node->data;
+ ovsdb_column_set_destroy(&mt->columns);
+ free(mt);
+ }
+ shash_destroy(&m->tables);
+ hmap_remove(&m->session->monitors, &m->node);
+ free(m);
+}
+
+static const struct ovsdb_replica_class ovsdb_jsonrpc_replica_class = {
+ ovsdb_jsonrpc_monitor_commit,
+ ovsdb_jsonrpc_monitor_destroy
+};
--- /dev/null
- do_list_dbs(int argc UNUSED, char *argv[])
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "command-line.h"
+#include "column.h"
+#include "compiler.h"
+#include "daemon.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb.h"
+#include "ovsdb-error.h"
+#include "stream.h"
+#include "stream-ssl.h"
+#include "table.h"
+#include "timeval.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_client
+
+/* --format: Output formatting. */
+static enum {
+ FMT_TABLE, /* Textual table. */
+ FMT_HTML, /* HTML table. */
+ FMT_CSV /* Comma-separated lines. */
+} output_format;
+
+/* --wide: For --format=table, the maximum output width. */
+static int output_width;
+
+/* --no-headings: Whether table output should include headings. */
+static int output_headings = true;
+
+/* --pretty: Flags to pass to json_to_string(). */
+static int json_flags = JSSF_SORT;
+
+static const struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+ proctitle_init(argc, argv);
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+ parse_options(argc, argv);
+ signal(SIGPIPE, SIG_IGN);
+ run_command(argc - optind, argv + optind, all_commands);
+ return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+ enum {
+ OPT_BOOTSTRAP_CA_CERT = UCHAR_MAX + 1
+ };
+ static struct option long_options[] = {
+ {"wide", no_argument, &output_width, INT_MAX},
+ {"format", required_argument, 0, 'f'},
+ {"no-headings", no_argument, &output_headings, 0},
+ {"pretty", no_argument, &json_flags, JSSF_PRETTY | JSSF_SORT},
+ {"verbose", optional_argument, 0, 'v'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ DAEMON_LONG_OPTIONS,
+#ifdef HAVE_OPENSSL
+ {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT},
+ STREAM_SSL_LONG_OPTIONS
+#endif
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+
+ output_width = isatty(fileno(stdout)) ? 79 : INT_MAX;
+ for (;;) {
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'f':
+ if (!strcmp(optarg, "table")) {
+ output_format = FMT_TABLE;
+ } else if (!strcmp(optarg, "html")) {
+ output_format = FMT_HTML;
+ } else if (!strcmp(optarg, "csv")) {
+ output_format = FMT_CSV;
+ } else {
+ ovs_fatal(0, "unknown output format \"%s\"", optarg);
+ }
+ break;
+
+ case 'w':
+ output_width = INT_MAX;
+ break;
+
+ case 'h':
+ usage();
+
+ case 'V':
+ OVS_PRINT_VERSION(0, 0);
+ exit(EXIT_SUCCESS);
+
+ case 'v':
+ vlog_set_verbosity(optarg);
+ break;
+
+ DAEMON_OPTION_HANDLERS
+
+#ifdef HAVE_OPENSSL
+ STREAM_SSL_OPTION_HANDLERS
+
+ case OPT_BOOTSTRAP_CA_CERT:
+ stream_ssl_set_ca_cert_file(optarg, true);
+ break;
+#endif
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ case 0:
+ /* getopt_long() already set the value for us. */
+ break;
+
+ default:
+ abort();
+ }
+ }
+ free(short_options);
+}
+
+static void
+usage(void)
+{
+ printf("%s: Open vSwitch database JSON-RPC client\n"
+ "usage: %s [OPTIONS] COMMAND [ARG...]\n"
+ "\nValid commands are:\n"
+ "\n list-dbs SERVER\n"
+ " list databases available on SERVER\n"
+ "\n get-schema SERVER DATABASE\n"
+ " retrieve schema for DATABASE from SERVER\n"
+ "\n list-tables SERVER DATABSE\n"
+ " list tables for DATABSAE on SERVER\n"
+ "\n list-columns SERVER DATABASE [TABLE]\n"
+ " list columns in TABLE (or all tables) in DATABASE on SERVER\n"
+ "\n transact SERVER TRANSACTION\n"
+ " run TRANSACTION (a JSON array of operations) on SERVER\n"
+ " and print the results as JSON on stdout\n"
+ "\n monitor SERVER DATABASE TABLE [COLUMN,...] [SELECT,...]\n"
+ " monitor contents of (COLUMNs in) TABLE in DATABASE on SERVER\n"
+ " Valid SELECTs are: initial, insert, delete, modify\n",
+ program_name, program_name);
+ stream_usage("SERVER", true, true, true);
+ printf("\nOutput formatting options:\n"
+ " -f, --format=FORMAT set output formatting to FORMAT\n"
+ " (\"table\", \"html\", or \"csv\"\n"
+ " --wide don't limit TTY lines to 79 bytes\n"
+ " --no-headings omit table heading row\n"
+ " --pretty pretty-print JSON in output");
+ daemon_usage();
+ vlog_usage();
+ printf("\nOther options:\n"
+ " -h, --help display this help message\n"
+ " -V, --version display version information\n");
+ exit(EXIT_SUCCESS);
+}
+\f
+static struct json *
+parse_json(const char *s)
+{
+ struct json *json = json_from_string(s);
+ if (json->type == JSON_STRING) {
+ ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+ }
+ return json;
+}
+
+static struct jsonrpc *
+open_jsonrpc(const char *server)
+{
+ struct stream *stream;
+ int error;
+
+ error = stream_open_block(server, &stream);
+ if (error == EAFNOSUPPORT) {
+ struct pstream *pstream;
+
+ error = pstream_open(server, &pstream);
+ if (error) {
+ ovs_fatal(error, "failed to connect or listen to \"%s\"", server);
+ }
+
+ VLOG_INFO("%s: waiting for connection...", server);
+ error = pstream_accept_block(pstream, &stream);
+ if (error) {
+ ovs_fatal(error, "failed to accept connection on \"%s\"", server);
+ }
+
+ pstream_close(pstream);
+ } else if (error) {
+ ovs_fatal(error, "failed to connect to \"%s\"", server);
+ }
+
+ return jsonrpc_open(stream);
+}
+
+static void
+print_json(struct json *json)
+{
+ char *string = json_to_string(json, json_flags);
+ fputs(string, stdout);
+ free(string);
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+ print_json(json);
+ json_destroy(json);
+}
+
+static void
+check_ovsdb_error(struct ovsdb_error *error)
+{
+ if (error) {
+ ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+ }
+}
+
+static struct ovsdb_schema *
+fetch_schema_from_rpc(struct jsonrpc *rpc, const char *database)
+{
+ struct jsonrpc_msg *request, *reply;
+ struct ovsdb_schema *schema;
+ int error;
+
+ request = jsonrpc_create_request("get_schema",
+ json_array_create_1(
+ json_string_create(database)),
+ NULL);
+ error = jsonrpc_transact_block(rpc, request, &reply);
+ if (error) {
+ ovs_fatal(error, "transaction failed");
+ }
+ check_ovsdb_error(ovsdb_schema_from_json(reply->result, &schema));
+ jsonrpc_msg_destroy(reply);
+
+ return schema;
+}
+
+static struct ovsdb_schema *
+fetch_schema(const char *server, const char *database)
+{
+ struct ovsdb_schema *schema;
+ struct jsonrpc *rpc;
+
+ rpc = open_jsonrpc(server);
+ schema = fetch_schema_from_rpc(rpc, database);
+ jsonrpc_close(rpc);
+
+ return schema;
+}
+\f
+struct column {
+ char *heading;
+ int width;
+};
+
+struct table {
+ char **cells;
+ struct column *columns;
+ size_t n_columns, allocated_columns;
+ size_t n_rows, allocated_rows;
+ size_t current_column;
+};
+
+static void
+table_init(struct table *table)
+{
+ memset(table, 0, sizeof *table);
+}
+
+static void
+table_destroy(struct table *table)
+{
+ size_t i;
+
+ for (i = 0; i < table->n_columns; i++) {
+ free(table->columns[i].heading);
+ }
+ free(table->columns);
+
+ for (i = 0; i < table->n_columns * table->n_rows; i++) {
+ free(table->cells[i]);
+ }
+ free(table->cells);
+}
+
+static void
+table_add_column(struct table *table, const char *heading, ...)
+ PRINTF_FORMAT(2, 3);
+
+static void
+table_add_column(struct table *table, const char *heading, ...)
+{
+ struct column *column;
+ va_list args;
+
+ assert(!table->n_rows);
+ if (table->n_columns >= table->allocated_columns) {
+ table->columns = x2nrealloc(table->columns, &table->allocated_columns,
+ sizeof *table->columns);
+ }
+ column = &table->columns[table->n_columns++];
+
+ va_start(args, heading);
+ column->heading = xvasprintf(heading, args);
+ column->width = strlen(column->heading);
+ va_end(args);
+}
+
+static char **
+table_cell__(const struct table *table, size_t row, size_t column)
+{
+ return &table->cells[column + row * table->n_columns];
+}
+
+static void
+table_add_row(struct table *table)
+{
+ size_t x, y;
+
+ if (table->n_rows >= table->allocated_rows) {
+ table->cells = x2nrealloc(table->cells, &table->allocated_rows,
+ table->n_columns * sizeof *table->cells);
+ }
+
+ y = table->n_rows++;
+ table->current_column = 0;
+ for (x = 0; x < table->n_columns; x++) {
+ *table_cell__(table, y, x) = NULL;
+ }
+}
+
+static void
+table_add_cell_nocopy(struct table *table, char *s)
+{
+ size_t x, y;
+ int length;
+
+ assert(table->n_rows > 0);
+ assert(table->current_column < table->n_columns);
+
+ x = table->current_column++;
+ y = table->n_rows - 1;
+ *table_cell__(table, y, x) = s;
+
+ length = strlen(s);
+ if (length > table->columns[x].width) {
+ table->columns[x].width = length;
+ }
+}
+
+static void
+table_add_cell(struct table *table, const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ table_add_cell_nocopy(table, xvasprintf(format, args));
+ va_end(args);
+}
+
+static void
+table_print_table_line__(struct ds *line, size_t max_width)
+{
+ ds_truncate(line, max_width);
+ puts(ds_cstr(line));
+ ds_clear(line);
+}
+
+static void
+table_print_table__(const struct table *table)
+{
+ struct ds line = DS_EMPTY_INITIALIZER;
+ size_t x, y;
+
+ if (output_headings) {
+ for (x = 0; x < table->n_columns; x++) {
+ const struct column *column = &table->columns[x];
+ if (x) {
+ ds_put_char(&line, ' ');
+ }
+ ds_put_format(&line, "%-*s", column->width, column->heading);
+ }
+ table_print_table_line__(&line, output_width);
+
+ for (x = 0; x < table->n_columns; x++) {
+ const struct column *column = &table->columns[x];
+ int i;
+
+ if (x) {
+ ds_put_char(&line, ' ');
+ }
+ for (i = 0; i < column->width; i++) {
+ ds_put_char(&line, '-');
+ }
+ }
+ table_print_table_line__(&line, output_width);
+ }
+
+ for (y = 0; y < table->n_rows; y++) {
+ for (x = 0; x < table->n_columns; x++) {
+ const char *cell = *table_cell__(table, y, x);
+ if (x) {
+ ds_put_char(&line, ' ');
+ }
+ ds_put_format(&line, "%-*s", table->columns[x].width, cell);
+ }
+ table_print_table_line__(&line, output_width);
+ }
+
+ ds_destroy(&line);
+}
+
+static void
+table_print_html_cell__(const char *element, const char *content)
+{
+ const char *p;
+
+ printf(" <%s>", element);
+ for (p = content; *p != '\0'; p++) {
+ switch (*p) {
+ case '&':
+ fputs("&", stdout);
+ break;
+ case '<':
+ fputs("<", stdout);
+ break;
+ case '>':
+ fputs(">", stdout);
+ break;
+ default:
+ putchar(*p);
+ break;
+ }
+ }
+ printf("</%s>\n", element);
+}
+
+static void
+table_print_html__(const struct table *table)
+{
+ size_t x, y;
+
+ fputs("<table>\n", stdout);
+
+ if (output_headings) {
+ fputs(" <tr>\n", stdout);
+ for (x = 0; x < table->n_columns; x++) {
+ const struct column *column = &table->columns[x];
+ table_print_html_cell__("th", column->heading);
+ }
+ fputs(" </tr>\n", stdout);
+ }
+
+ for (y = 0; y < table->n_rows; y++) {
+ fputs(" <tr>\n", stdout);
+ for (x = 0; x < table->n_columns; x++) {
+ table_print_html_cell__("td", *table_cell__(table, y, x));
+ }
+ fputs(" </tr>\n", stdout);
+ }
+
+ fputs("</table>\n", stdout);
+}
+
+static void
+table_print_csv_cell__(const char *content)
+{
+ const char *p;
+
+ if (!strpbrk(content, "\n\",")) {
+ fputs(content, stdout);
+ } else {
+ putchar('"');
+ for (p = content; *p != '\0'; p++) {
+ switch (*p) {
+ case '"':
+ fputs("\"\"", stdout);
+ break;
+ default:
+ putchar(*p);
+ break;
+ }
+ }
+ putchar('"');
+ }
+}
+
+static void
+table_print_csv__(const struct table *table)
+{
+ size_t x, y;
+
+ if (output_headings) {
+ for (x = 0; x < table->n_columns; x++) {
+ const struct column *column = &table->columns[x];
+ if (x) {
+ putchar(',');
+ }
+ table_print_csv_cell__(column->heading);
+ }
+ putchar('\n');
+ }
+
+ for (y = 0; y < table->n_rows; y++) {
+ for (x = 0; x < table->n_columns; x++) {
+ if (x) {
+ putchar(',');
+ }
+ table_print_csv_cell__(*table_cell__(table, y, x));
+ }
+ putchar('\n');
+ }
+}
+
+static void
+table_print(const struct table *table)
+{
+ switch (output_format) {
+ case FMT_TABLE:
+ table_print_table__(table);
+ break;
+
+ case FMT_HTML:
+ table_print_html__(table);
+ break;
+
+ case FMT_CSV:
+ table_print_csv__(table);
+ break;
+ }
+}
+\f
+static void
- do_get_schema(int argc UNUSED, char *argv[])
++do_list_dbs(int argc OVS_UNUSED, char *argv[])
+{
+ struct jsonrpc_msg *request, *reply;
+ struct jsonrpc *rpc;
+ int error;
+ size_t i;
+
+ rpc = open_jsonrpc(argv[1]);
+ request = jsonrpc_create_request("list_dbs", json_array_create_empty(),
+ NULL);
+ error = jsonrpc_transact_block(rpc, request, &reply);
+ if (error) {
+ ovs_fatal(error, "transaction failed");
+ }
+
+ if (reply->result->type != JSON_ARRAY) {
+ ovs_fatal(0, "list_dbs response is not array");
+ }
+
+ for (i = 0; i < reply->result->u.array.n; i++) {
+ const struct json *name = reply->result->u.array.elems[i];
+
+ if (name->type != JSON_STRING) {
+ ovs_fatal(0, "list_dbs response %zu is not string", i);
+ }
+ puts(name->u.string);
+ }
+ jsonrpc_msg_destroy(reply);
+}
+
+static void
- do_list_tables(int argc UNUSED, char *argv[])
++do_get_schema(int argc OVS_UNUSED, char *argv[])
+{
+ struct ovsdb_schema *schema = fetch_schema(argv[1], argv[2]);
+ print_and_free_json(ovsdb_schema_to_json(schema));
+ ovsdb_schema_destroy(schema);
+}
+
+static void
- do_list_columns(int argc UNUSED, char *argv[])
++do_list_tables(int argc OVS_UNUSED, char *argv[])
+{
+ struct ovsdb_schema *schema;
+ struct shash_node *node;
+ struct table t;
+
+ schema = fetch_schema(argv[1], argv[2]);
+ table_init(&t);
+ table_add_column(&t, "Table");
+ table_add_column(&t, "Comment");
+ SHASH_FOR_EACH (node, &schema->tables) {
+ struct ovsdb_table_schema *ts = node->data;
+
+ table_add_row(&t);
+ table_add_cell(&t, ts->name);
+ if (ts->comment) {
+ table_add_cell(&t, ts->comment);
+ }
+ }
+ ovsdb_schema_destroy(schema);
+ table_print(&t);
+}
+
+static void
- do_transact(int argc UNUSED, char *argv[])
++do_list_columns(int argc OVS_UNUSED, char *argv[])
+{
+ const char *table_name = argv[3];
+ struct ovsdb_schema *schema;
+ struct shash_node *table_node;
+ struct table t;
+
+ schema = fetch_schema(argv[1], argv[2]);
+ table_init(&t);
+ if (!table_name) {
+ table_add_column(&t, "Table");
+ }
+ table_add_column(&t, "Column");
+ table_add_column(&t, "Type");
+ table_add_column(&t, "Comment");
+ SHASH_FOR_EACH (table_node, &schema->tables) {
+ struct ovsdb_table_schema *ts = table_node->data;
+
+ if (!table_name || !strcmp(table_name, ts->name)) {
+ struct shash_node *column_node;
+
+ SHASH_FOR_EACH (column_node, &ts->columns) {
+ const struct ovsdb_column *column = column_node->data;
+ struct json *type = ovsdb_type_to_json(&column->type);
+
+ table_add_row(&t);
+ if (!table_name) {
+ table_add_cell(&t, ts->name);
+ }
+ table_add_cell(&t, column->name);
+ table_add_cell_nocopy(&t, json_to_string(type, JSSF_SORT));
+ if (column->comment) {
+ table_add_cell(&t, column->comment);
+ }
+
+ json_destroy(type);
+ }
+ }
+ }
+ ovsdb_schema_destroy(schema);
+ table_print(&t);
+}
+
+static void
- do_help(int argc UNUSED, char *argv[] UNUSED)
++do_transact(int argc OVS_UNUSED, char *argv[])
+{
+ struct jsonrpc_msg *request, *reply;
+ struct json *transaction;
+ struct jsonrpc *rpc;
+ int error;
+
+ transaction = parse_json(argv[2]);
+
+ rpc = open_jsonrpc(argv[1]);
+ request = jsonrpc_create_request("transact", transaction, NULL);
+ error = jsonrpc_transact_block(rpc, request, &reply);
+ if (error) {
+ ovs_fatal(error, "transaction failed");
+ }
+ if (reply->error) {
+ ovs_fatal(error, "transaction returned error: %s",
+ json_to_string(reply->error, json_flags));
+ }
+ print_json(reply->result);
+ putchar('\n');
+ jsonrpc_msg_destroy(reply);
+ jsonrpc_close(rpc);
+}
+
+static void
+monitor_print_row(struct json *row, const char *type, const char *uuid,
+ const struct ovsdb_column_set *columns, struct table *t)
+{
+ size_t i;
+
+ if (!row) {
+ ovs_error(0, "missing %s row", type);
+ return;
+ } else if (row->type != JSON_OBJECT) {
+ ovs_error(0, "<row> is not object");
+ return;
+ }
+
+ table_add_row(t);
+ table_add_cell(t, uuid);
+ table_add_cell(t, type);
+ for (i = 0; i < columns->n_columns; i++) {
+ const struct ovsdb_column *column = columns->columns[i];
+ struct json *value = shash_find_data(json_object(row), column->name);
+ if (value) {
+ table_add_cell_nocopy(t, json_to_string(value, JSSF_SORT));
+ } else {
+ table_add_cell(t, "");
+ }
+ }
+}
+
+static void
+monitor_print(struct json *table_updates,
+ const struct ovsdb_table_schema *table,
+ const struct ovsdb_column_set *columns, bool initial)
+{
+ struct json *table_update;
+ struct shash_node *node;
+ struct table t;
+ size_t i;
+
+ table_init(&t);
+
+ if (table_updates->type != JSON_OBJECT) {
+ ovs_error(0, "<table-updates> is not object");
+ return;
+ }
+ table_update = shash_find_data(json_object(table_updates), table->name);
+ if (!table_update) {
+ return;
+ }
+ if (table_update->type != JSON_OBJECT) {
+ ovs_error(0, "<table-update> is not object");
+ return;
+ }
+
+ table_add_column(&t, "row");
+ table_add_column(&t, "action");
+ for (i = 0; i < columns->n_columns; i++) {
+ table_add_column(&t, "%s", columns->columns[i]->name);
+ }
+ SHASH_FOR_EACH (node, json_object(table_update)) {
+ struct json *row_update = node->data;
+ struct json *old, *new;
+
+ if (row_update->type != JSON_OBJECT) {
+ ovs_error(0, "<row-update> is not object");
+ continue;
+ }
+ old = shash_find_data(json_object(row_update), "old");
+ new = shash_find_data(json_object(row_update), "new");
+ if (initial) {
+ monitor_print_row(new, "initial", node->name, columns, &t);
+ } else if (!old) {
+ monitor_print_row(new, "insert", node->name, columns, &t);
+ } else if (!new) {
+ monitor_print_row(old, "delete", node->name, columns, &t);
+ } else {
+ monitor_print_row(old, "old", node->name, columns, &t);
+ monitor_print_row(new, "new", "", columns, &t);
+ }
+ }
+ table_print(&t);
+ table_destroy(&t);
+}
+
+static void
+do_monitor(int argc, char *argv[])
+{
+ const char *server = argv[1];
+ const char *database = argv[2];
+ const char *table_name = argv[3];
+ struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+ struct ovsdb_table_schema *table;
+ struct ovsdb_schema *schema;
+ struct jsonrpc_msg *request;
+ struct jsonrpc *rpc;
+ struct json *select, *monitor, *monitor_request, *monitor_requests,
+ *request_id;
+
+ rpc = open_jsonrpc(server);
+
+ schema = fetch_schema_from_rpc(rpc, database);
+ table = shash_find_data(&schema->tables, table_name);
+ if (!table) {
+ ovs_fatal(0, "%s: %s does not have a table named \"%s\"",
+ server, database, table_name);
+ }
+
+ if (argc >= 5 && *argv[4] != '\0') {
+ char *save_ptr = NULL;
+ char *token;
+
+ for (token = strtok_r(argv[4], ",", &save_ptr); token != NULL;
+ token = strtok_r(NULL, ",", &save_ptr)) {
+ const struct ovsdb_column *column;
+ column = ovsdb_table_schema_get_column(table, token);
+ if (!column) {
+ ovs_fatal(0, "%s: table \"%s\" in %s does not have a "
+ "column named \"%s\"",
+ server, table_name, database, token);
+ }
+ ovsdb_column_set_add(&columns, column);
+ }
+ } else {
+ struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &table->columns) {
+ const struct ovsdb_column *column = node->data;
+ if (column->index != OVSDB_COL_UUID) {
+ ovsdb_column_set_add(&columns, column);
+ }
+ }
+ }
+
+ if (argc >= 6 && *argv[5] != '\0') {
+ char *save_ptr = NULL;
+ char *token;
+
+ select = json_object_create();
+ for (token = strtok_r(argv[5], ",", &save_ptr); token != NULL;
+ token = strtok_r(NULL, ",", &save_ptr)) {
+ json_object_put(select, token, json_boolean_create(true));
+ }
+ } else {
+ select = NULL;
+ }
+
+ monitor_request = json_object_create();
+ json_object_put(monitor_request,
+ "columns", ovsdb_column_set_to_json(&columns));
+ if (select) {
+ json_object_put(monitor_request, "select", select);
+ }
+
+ monitor_requests = json_object_create();
+ json_object_put(monitor_requests, table_name, monitor_request);
+
+ monitor = json_array_create_3(json_string_create(database),
+ json_null_create(), monitor_requests);
+ request = jsonrpc_create_request("monitor", monitor, NULL);
+ request_id = json_clone(request->id);
+ jsonrpc_send(rpc, request);
+ for (;;) {
+ struct jsonrpc_msg *msg;
+ int error;
+
+ error = jsonrpc_recv_block(rpc, &msg);
+ if (error) {
+ ovsdb_schema_destroy(schema);
+ ovs_fatal(error, "%s: receive failed", server);
+ }
+
+ if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) {
+ jsonrpc_send(rpc, jsonrpc_create_reply(json_clone(msg->params),
+ msg->id));
+ } else if (msg->type == JSONRPC_REPLY
+ && json_equal(msg->id, request_id)) {
+ monitor_print(msg->result, table, &columns, true);
+ fflush(stdout);
+ if (get_detach()) {
+ /* daemonize() closes the standard file descriptors. We output
+ * to stdout, so we need to save and restore STDOUT_FILENO. */
+ int fd = dup(STDOUT_FILENO);
+ daemonize();
+ dup2(fd, STDOUT_FILENO);
+ close(fd);
+ }
+ } else if (msg->type == JSONRPC_NOTIFY
+ && !strcmp(msg->method, "update")) {
+ struct json *params = msg->params;
+ if (params->type == JSON_ARRAY
+ && params->u.array.n == 2
+ && params->u.array.elems[0]->type == JSON_NULL) {
+ monitor_print(params->u.array.elems[1],
+ table, &columns, false);
+ fflush(stdout);
+ }
+ }
+ jsonrpc_msg_destroy(msg);
+ }
+}
+
+static void
++do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ usage();
+}
+
+static const struct command all_commands[] = {
+ { "list-dbs", 1, 1, do_list_dbs },
+ { "get-schema", 2, 2, do_get_schema },
+ { "list-tables", 2, 2, do_list_tables },
+ { "list-columns", 2, 3, do_list_columns },
+ { "transact", 2, 2, do_transact },
+ { "monitor", 3, 5, do_monitor },
+ { "help", 0, INT_MAX, do_help },
+ { NULL, 0, 0, NULL },
+};
--- /dev/null
- static void UNUSED
+#! @PYTHON@
+
+import getopt
+import os
+import re
+import sys
+
+sys.path.insert(0, "@abs_top_srcdir@/ovsdb")
+import simplejson as json
+
+argv0 = sys.argv[0]
+
+class Error(Exception):
+ def __init__(self, msg):
+ Exception.__init__(self)
+ self.msg = msg
+
+def getMember(json, name, validTypes, description, default=None):
+ if name in json:
+ member = json[name]
+ if type(member) not in validTypes:
+ raise Error("%s: type mismatch for '%s' member"
+ % (description, name))
+ return member
+ return default
+
+def mustGetMember(json, name, expectedType, description):
+ member = getMember(json, name, expectedType, description)
+ if member == None:
+ raise Error("%s: missing '%s' member" % (description, name))
+ return member
+
+class DbSchema:
+ def __init__(self, name, comment, tables, idlPrefix, idlHeader):
+ self.name = name
+ self.comment = comment
+ self.tables = tables
+ self.idlPrefix = idlPrefix
+ self.idlHeader = idlHeader
+
+ @staticmethod
+ def fromJson(json):
+ name = mustGetMember(json, 'name', [unicode], 'database')
+ comment = getMember(json, 'comment', [unicode], 'database')
+ tablesJson = mustGetMember(json, 'tables', [dict], 'database')
+ tables = {}
+ for tableName, tableJson in tablesJson.iteritems():
+ tables[tableName] = TableSchema.fromJson(tableJson,
+ "%s table" % tableName)
+ idlPrefix = mustGetMember(json, 'idlPrefix', [unicode], 'database')
+ idlHeader = mustGetMember(json, 'idlHeader', [unicode], 'database')
+ return DbSchema(name, comment, tables, idlPrefix, idlHeader)
+
+class TableSchema:
+ def __init__(self, comment, columns):
+ self.comment = comment
+ self.columns = columns
+
+ @staticmethod
+ def fromJson(json, description):
+ comment = getMember(json, 'comment', [unicode], description)
+ columnsJson = mustGetMember(json, 'columns', [dict], description)
+ columns = {}
+ for name, json in columnsJson.iteritems():
+ columns[name] = ColumnSchema.fromJson(
+ json, "column %s in %s" % (name, description))
+ return TableSchema(comment, columns)
+
+class ColumnSchema:
+ def __init__(self, comment, type, persistent):
+ self.comment = comment
+ self.type = type
+ self.persistent = persistent
+
+ @staticmethod
+ def fromJson(json, description):
+ comment = getMember(json, 'comment', [unicode], description)
+ type = Type.fromJson(mustGetMember(json, 'type', [dict, unicode],
+ description),
+ 'type of %s' % description)
+ ephemeral = getMember(json, 'ephemeral', [bool], description)
+ persistent = ephemeral != True
+ return ColumnSchema(comment, type, persistent)
+
+def escapeCString(src):
+ dst = ""
+ for c in src:
+ if c in "\\\"":
+ dst += "\\" + c
+ elif ord(c) < 32:
+ if c == '\n':
+ dst += '\\n'
+ elif c == '\r':
+ dst += '\\r'
+ elif c == '\a':
+ dst += '\\a'
+ elif c == '\b':
+ dst += '\\b'
+ elif c == '\f':
+ dst += '\\f'
+ elif c == '\t':
+ dst += '\\t'
+ elif c == '\v':
+ dst += '\\v'
+ else:
+ dst += '\\%03o' % ord(c)
+ else:
+ dst += c
+ return dst
+
+class BaseType:
+ def __init__(self, type, refTable=None, minInteger=None, maxInteger=None,
+ minReal=None, maxReal=None, reMatch=None, reComment=None,
+ minLength=None, maxLength=None):
+ self.type = type
+ self.refTable = refTable
+ self.minInteger = minInteger
+ self.maxInteger = maxInteger
+ self.minReal = minReal
+ self.maxReal = maxReal
+ self.reMatch = reMatch
+ self.reComment = reComment
+ self.minLength = minLength
+ self.maxLength = maxLength
+
+ @staticmethod
+ def fromJson(json, description):
+ if type(json) == unicode:
+ return BaseType(json)
+ else:
+ atomicType = mustGetMember(json, 'type', [unicode], description)
+ refTable = getMember(json, 'refTable', [unicode], description)
+ minInteger = getMember(json, 'minInteger', [int, long], description)
+ maxInteger = getMember(json, 'maxInteger', [int, long], description)
+ minReal = getMember(json, 'minReal', [int, long, float], description)
+ maxReal = getMember(json, 'maxReal', [int, long, float], description)
+ reMatch = getMember(json, 'reMatch', [unicode], description)
+ reComment = getMember(json, 'reComment', [unicode], description)
+ minLength = getMember(json, 'minLength', [int], description)
+ maxLength = getMember(json, 'minLength', [int], description)
+ return BaseType(atomicType, refTable, minInteger, maxInteger, minReal, maxReal, reMatch, reComment, minLength, maxLength)
+
+ def toEnglish(self):
+ if self.type == 'uuid' and self.refTable:
+ return self.refTable
+ else:
+ return self.type
+
+ def toCType(self, prefix):
+ if self.refTable:
+ return "struct %s%s *" % (prefix, self.refTable.lower())
+ else:
+ return {'integer': 'int64_t ',
+ 'real': 'double ',
+ 'uuid': 'struct uuid ',
+ 'boolean': 'bool ',
+ 'string': 'char *'}[self.type]
+
+ def copyCValue(self, dst, src):
+ args = {'dst': dst, 'src': src}
+ if self.refTable:
+ return ("%(dst)s = %(src)s->header_.uuid;") % args
+ elif self.type == 'string':
+ return "%(dst)s = xstrdup(%(src)s);" % args
+ else:
+ return "%(dst)s = %(src)s;" % args
+
+ def initCDefault(self, var, isOptional):
+ if self.refTable:
+ return "%s = NULL;" % var
+ elif self.type == 'string' and not isOptional:
+ return "%s = \"\";" % var
+ else:
+ return {'integer': '%s = 0;',
+ 'real': '%s = 0.0;',
+ 'uuid': 'uuid_zero(&%s);',
+ 'boolean': '%s = false;',
+ 'string': '%s = NULL;'}[self.type] % var
+
+ def cInitBaseType(self, indent, var):
+ stmts = []
+ stmts.append('ovsdb_base_type_init(&%s, OVSDB_TYPE_%s);' % (
+ var, self.type.upper()),)
+ if self.type == 'integer':
+ if self.minInteger != None:
+ stmts.append('%s.u.integer.min = %d;' % (var, self.minInteger))
+ if self.maxInteger != None:
+ stmts.append('%s.u.integer.max = %d;' % (var, self.maxInteger))
+ elif self.type == 'real':
+ if self.minReal != None:
+ stmts.append('%s.u.real.min = %d;' % (var, self.minReal))
+ if self.maxReal != None:
+ stmts.append('%s.u.real.max = %d;' % (var, self.maxReal))
+ elif self.type == 'string':
+ if self.reMatch != None:
+ if self.reComment != None:
+ reComment = '"%s"' % escapeCString(self.reComment)
+ else:
+ reComment = NULL
+ stmts.append('do_set_regex(&%s, "%s", %s);' % (
+ var, escapeCString(self.reMatch), reComment))
+ if self.minLength != None:
+ stmts.append('%s.u.string.minLen = %d;' % (var, self.minLength))
+ if self.maxLength != None:
+ stmts.append('%s.u.string.maxLen = %d;' % (var, self.maxLength))
+ elif self.type == 'uuid':
+ if self.refTable != None:
+ stmts.append('%s.u.uuid.refTableName = "%s";' % (var, escapeCString(self.refTable)))
+ return '\n'.join([indent + stmt for stmt in stmts])
+
+class Type:
+ def __init__(self, key, value=None, min=1, max=1):
+ self.key = key
+ self.value = value
+ self.min = min
+ self.max = max
+
+ @staticmethod
+ def fromJson(json, description):
+ if type(json) == unicode:
+ return Type(BaseType(json))
+ else:
+ keyJson = mustGetMember(json, 'key', [dict, unicode], description)
+ key = BaseType.fromJson(keyJson, 'key in %s' % description)
+
+ valueJson = getMember(json, 'value', [dict, unicode], description)
+ if valueJson:
+ value = BaseType.fromJson(valueJson,
+ 'value in %s' % description)
+ else:
+ value = None
+
+ min = getMember(json, 'min', [int], description, 1)
+ max = getMember(json, 'max', [int, unicode], description, 1)
+ return Type(key, value, min, max)
+
+ def isScalar(self):
+ return self.min == 1 and self.max == 1 and not self.value
+
+ def isOptional(self):
+ return self.min == 0 and self.max == 1
+
+ def isOptionalPointer(self):
+ return (self.min == 0 and self.max == 1 and not self.value
+ and (self.key.type == 'string' or self.key.refTable))
+
+ def toEnglish(self):
+ keyName = self.key.toEnglish()
+ if self.value:
+ valueName = self.value.toEnglish()
+
+ if self.isScalar():
+ return keyName
+ elif self.isOptional():
+ if self.value:
+ return "optional %s-%s pair" % (keyName, valueName)
+ else:
+ return "optional %s" % keyName
+ else:
+ if self.max == "unlimited":
+ if self.min:
+ quantity = "%d or more " % self.min
+ else:
+ quantity = ""
+ elif self.min:
+ quantity = "%d to %d " % (self.min, self.max)
+ else:
+ quantity = "up to %d " % self.max
+
+ if self.value:
+ return "map of %s%s-%s pairs" % (quantity, keyName, valueName)
+ else:
+ return "set of %s%s" % (quantity, keyName)
+
+ def cDeclComment(self):
+ if self.min == 1 and self.max == 1 and self.key.type == "string":
+ return "\t/* Always nonnull. */"
+ else:
+ return ""
+
+ def cInitType(self, indent, var):
+ initKey = self.key.cInitBaseType(indent, "%s.key" % var)
+ if self.value:
+ initValue = self.value.cInitBaseType(indent, "%s.value" % var)
+ else:
+ initValue = ('%sovsdb_base_type_init(&%s.value, '
+ 'OVSDB_TYPE_VOID);' % (indent, var))
+ initMin = "%s%s.n_min = %s;" % (indent, var, self.min)
+ if self.max == "unlimited":
+ max = "UINT_MAX"
+ else:
+ max = self.max
+ initMax = "%s%s.n_max = %s;" % (indent, var, max)
+ return "\n".join((initKey, initValue, initMin, initMax))
+
+def parseSchema(filename):
+ return DbSchema.fromJson(json.load(open(filename, "r")))
+
+def annotateSchema(schemaFile, annotationFile):
+ schemaJson = json.load(open(schemaFile, "r"))
+ execfile(annotationFile, globals(), {"s": schemaJson})
+ json.dump(schemaJson, sys.stdout)
+
+def constify(cType, const):
+ if (const
+ and cType.endswith('*') and not cType.endswith('**')
+ and (cType.startswith('struct uuid') or cType.startswith('char'))):
+ return 'const %s' % cType
+ else:
+ return cType
+
+def cMembers(prefix, columnName, column, const):
+ type = column.type
+ if type.min == 1 and type.max == 1:
+ singleton = True
+ pointer = ''
+ else:
+ singleton = False
+ if type.isOptionalPointer():
+ pointer = ''
+ else:
+ pointer = '*'
+
+ if type.value:
+ key = {'name': "key_%s" % columnName,
+ 'type': constify(type.key.toCType(prefix) + pointer, const),
+ 'comment': ''}
+ value = {'name': "value_%s" % columnName,
+ 'type': constify(type.value.toCType(prefix) + pointer, const),
+ 'comment': ''}
+ members = [key, value]
+ else:
+ m = {'name': columnName,
+ 'type': constify(type.key.toCType(prefix) + pointer, const),
+ 'comment': type.cDeclComment()}
+ members = [m]
+
+ if not singleton and not type.isOptionalPointer():
+ members.append({'name': 'n_%s' % columnName,
+ 'type': 'size_t ',
+ 'comment': ''})
+ return members
+
+def printCIDLHeader(schemaFile):
+ schema = parseSchema(schemaFile)
+ prefix = schema.idlPrefix
+ print '''\
+/* Generated automatically -- do not modify! -*- buffer-read-only: t -*- */
+
+#ifndef %(prefix)sIDL_HEADER
+#define %(prefix)sIDL_HEADER 1
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include "ovsdb-idl-provider.h"
+#include "uuid.h"''' % {'prefix': prefix.upper()}
+
+ for tableName, table in sorted(schema.tables.iteritems()):
+ structName = "%s%s" % (prefix, tableName.lower())
+
+ print "\f"
+ print "/* %s table. */" % tableName
+ print "struct %s {" % structName
+ print "\tstruct ovsdb_idl_row header_;"
+ for columnName, column in sorted(table.columns.iteritems()):
+ print "\n\t/* %s column. */" % columnName
+ for member in cMembers(prefix, columnName, column, False):
+ print "\t%(type)s%(name)s;%(comment)s" % member
+ print "};"
+
+ # Column indexes.
+ printEnum(["%s_COL_%s" % (structName.upper(), columnName.upper())
+ for columnName in sorted(table.columns)]
+ + ["%s_N_COLUMNS" % structName.upper()])
+
+ print
+ for columnName in table.columns:
+ print "#define %(s)s_col_%(c)s (%(s)s_columns[%(S)s_COL_%(C)s])" % {
+ 's': structName,
+ 'S': structName.upper(),
+ 'c': columnName,
+ 'C': columnName.upper()}
+
+ print "\nextern struct ovsdb_idl_column %s_columns[%s_N_COLUMNS];" % (structName, structName.upper())
+
+ print '''
+const struct %(s)s *%(s)s_first(const struct ovsdb_idl *);
+const struct %(s)s *%(s)s_next(const struct %(s)s *);
+#define %(S)s_FOR_EACH(ROW, IDL) for ((ROW) = %(s)s_first(IDL); (ROW); (ROW) = %(s)s_next(ROW))
+
+void %(s)s_delete(const struct %(s)s *);
+struct %(s)s *%(s)s_insert(struct ovsdb_idl_txn *);
+''' % {'s': structName, 'S': structName.upper()}
+
+ for columnName, column in sorted(table.columns.iteritems()):
+ print 'void %(s)s_verify_%(c)s(const struct %(s)s *);' % {'s': structName, 'c': columnName}
+
+ print
+ for columnName, column in sorted(table.columns.iteritems()):
+
+ print 'void %(s)s_set_%(c)s(const struct %(s)s *,' % {'s': structName, 'c': columnName},
+ args = ['%(type)s%(name)s' % member for member
+ in cMembers(prefix, columnName, column, True)]
+ print '%s);' % ', '.join(args)
+
+ # Table indexes.
+ printEnum(["%sTABLE_%s" % (prefix.upper(), tableName.upper()) for tableName in sorted(schema.tables)] + ["%sN_TABLES" % prefix.upper()])
+ print
+ for tableName in schema.tables:
+ print "#define %(p)stable_%(t)s (%(p)stable_classes[%(P)sTABLE_%(T)s])" % {
+ 'p': prefix,
+ 'P': prefix.upper(),
+ 't': tableName.lower(),
+ 'T': tableName.upper()}
+ print "\nextern struct ovsdb_idl_table_class %stable_classes[%sN_TABLES];" % (prefix, prefix.upper())
+
+ print "\nextern struct ovsdb_idl_class %sidl_class;" % prefix
+ print "\nvoid %sinit(void);" % prefix
+ print "\n#endif /* %(prefix)sIDL_HEADER */" % {'prefix': prefix.upper()}
+
+def printEnum(members):
+ if len(members) == 0:
+ return
+
+ print "\nenum {";
+ for member in members[:-1]:
+ print " %s," % member
+ print " %s" % members[-1]
+ print "};"
+
+def printCIDLSource(schemaFile):
+ schema = parseSchema(schemaFile)
+ prefix = schema.idlPrefix
+ print '''\
+/* Generated automatically -- do not modify! -*- buffer-read-only: t -*- */
+
+#include <config.h>
+#include %s
+#include <assert.h>
+#include <limits.h>
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+
+static bool inited;
+
- %(s)s_unparse_%(c)s(struct ovsdb_idl_row *row UNUSED)
++static void OVS_UNUSED
+do_set_regex(struct ovsdb_base_type *base, const char *reMatch,
+ const char *reComment)
+{
+ struct ovsdb_error *error;
+
+ error = ovsdb_base_type_set_regex(base, reMatch, reComment);
+ if (error) {
+ char *s = ovsdb_error_to_string(error);
+ ovs_error(0, "%%s", s);
+ free(s);
+ ovsdb_error_destroy(error);
+ }
+}''' % schema.idlHeader
+
+ # Cast functions.
+ for tableName, table in sorted(schema.tables.iteritems()):
+ structName = "%s%s" % (prefix, tableName.lower())
+ print '''
+static struct %(s)s *
+%(s)s_cast(const struct ovsdb_idl_row *row)
+{
+ return row ? CONTAINER_OF(row, struct %(s)s, header_) : NULL;
+}\
+''' % {'s': structName}
+
+
+ for tableName, table in sorted(schema.tables.iteritems()):
+ structName = "%s%s" % (prefix, tableName.lower())
+ print "\f"
+ if table.comment != None:
+ print "/* %s table (%s). */" % (tableName, table.comment)
+ else:
+ print "/* %s table. */" % (tableName)
+
+ # Parse functions.
+ for columnName, column in sorted(table.columns.iteritems()):
+ print '''
+static void
+%(s)s_parse_%(c)s(struct ovsdb_idl_row *row_, const struct ovsdb_datum *datum)
+{
+ struct %(s)s *row = %(s)s_cast(row_);''' % {'s': structName,
+ 'c': columnName}
+
+ type = column.type
+ if type.value:
+ keyVar = "row->key_%s" % columnName
+ valueVar = "row->value_%s" % columnName
+ else:
+ keyVar = "row->%s" % columnName
+ valueVar = None
+
+ if (type.min == 1 and type.max == 1) or type.isOptionalPointer():
+ print
+ print " assert(inited);"
+ print " if (datum->n >= 1) {"
+ if not type.key.refTable:
+ print " %s = datum->keys[0].%s;" % (keyVar, type.key.type)
+ else:
+ print " %s = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[0].uuid));" % (keyVar, prefix, type.key.refTable.lower(), prefix, prefix.upper(), type.key.refTable.upper())
+
+ if valueVar:
+ if type.value.refTable:
+ print " %s = datum->values[0].%s;" % (valueVar, type.value.type)
+ else:
+ print " %s = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[0].uuid));" % (valueVar, prefix, type.value.refTable.lower(), prefix, prefix.upper(), type.value.refTable.upper())
+ print " } else {"
+ print " %s" % type.key.initCDefault(keyVar, type.min == 0)
+ if valueVar:
+ print " %s" % type.value.initCDefault(valueVar, type.min == 0)
+ print " }"
+ else:
+ if type.max != 'unlimited':
+ print " size_t n = MIN(%d, datum->n);" % type.max
+ nMax = "n"
+ else:
+ nMax = "datum->n"
+ print " size_t i;"
+ print
+ print " assert(inited);"
+ print " %s = NULL;" % keyVar
+ if valueVar:
+ print " %s = NULL;" % valueVar
+ print " row->n_%s = 0;" % columnName
+ print " for (i = 0; i < %s; i++) {" % nMax
+ refs = []
+ if type.key.refTable:
+ print " struct %s%s *keyRow = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[i].uuid));" % (prefix, type.key.refTable.lower(), prefix, type.key.refTable.lower(), prefix, prefix.upper(), type.key.refTable.upper())
+ keySrc = "keyRow"
+ refs.append('keyRow')
+ else:
+ keySrc = "datum->keys[i].%s" % type.key.type
+ if type.value and type.value.refTable:
+ print " struct %s%s *valueRow = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[i].uuid));" % (prefix, type.value.refTable.lower(), prefix, type.value.refTable.lower(), prefix, prefix.upper(), type.value.refTable.upper())
+ valueSrc = "valueRow"
+ refs.append('valueRow')
+ elif valueVar:
+ valueSrc = "datum->values[i].%s" % type.value.type
+ if refs:
+ print " if (%s) {" % ' && '.join(refs)
+ indent = " "
+ else:
+ indent = " "
+ print "%sif (!row->n_%s) {" % (indent, columnName)
+ print "%s %s = xmalloc(%s * sizeof *%s);" % (indent, keyVar, nMax, keyVar)
+ if valueVar:
+ print "%s %s = xmalloc(%s * sizeof %s);" % (indent, valueVar, nMax, valueVar)
+ print "%s}" % indent
+ print "%s%s[row->n_%s] = %s;" % (indent, keyVar, columnName, keySrc)
+ if valueVar:
+ print "%s%s[row->n_%s] = %s;" % (indent, valueVar, columnName, valueSrc)
+ print "%srow->n_%s++;" % (indent, columnName)
+ if refs:
+ print " }"
+ print " }"
+ print "}"
+
+ # Unparse functions.
+ for columnName, column in sorted(table.columns.iteritems()):
+ type = column.type
+ if (type.min != 1 or type.max != 1) and not type.isOptionalPointer():
+ print '''
+static void
+%(s)s_unparse_%(c)s(struct ovsdb_idl_row *row_)
+{
+ struct %(s)s *row = %(s)s_cast(row_);
+
+ assert(inited);''' % {'s': structName, 'c': columnName}
+ if type.value:
+ keyVar = "row->key_%s" % columnName
+ valueVar = "row->value_%s" % columnName
+ else:
+ keyVar = "row->%s" % columnName
+ valueVar = None
+ print " free(%s);" % keyVar
+ if valueVar:
+ print " free(%s);" % valueVar
+ print '}'
+ else:
+ print '''
+static void
++%(s)s_unparse_%(c)s(struct ovsdb_idl_row *row OVS_UNUSED)
+{
+ /* Nothing to do. */
+}''' % {'s': structName, 'c': columnName}
+
+ # First, next functions.
+ print '''
+const struct %(s)s *
+%(s)s_first(const struct ovsdb_idl *idl)
+{
+ return %(s)s_cast(ovsdb_idl_first_row(idl, &%(p)stable_classes[%(P)sTABLE_%(T)s]));
+}
+
+const struct %(s)s *
+%(s)s_next(const struct %(s)s *row)
+{
+ return %(s)s_cast(ovsdb_idl_next_row(&row->header_));
+}''' % {'s': structName,
+ 'p': prefix,
+ 'P': prefix.upper(),
+ 'T': tableName.upper()}
+
+ print '''
+void
+%(s)s_delete(const struct %(s)s *row)
+{
+ ovsdb_idl_txn_delete(&row->header_);
+}
+
+struct %(s)s *
+%(s)s_insert(struct ovsdb_idl_txn *txn)
+{
+ return %(s)s_cast(ovsdb_idl_txn_insert(txn, &%(p)stable_classes[%(P)sTABLE_%(T)s]));
+}
+''' % {'s': structName,
+ 'p': prefix,
+ 'P': prefix.upper(),
+ 'T': tableName.upper()}
+
+ # Verify functions.
+ for columnName, column in sorted(table.columns.iteritems()):
+ print '''
+void
+%(s)s_verify_%(c)s(const struct %(s)s *row)
+{
+ assert(inited);
+ ovsdb_idl_txn_verify(&row->header_, &%(s)s_columns[%(S)s_COL_%(C)s]);
+}''' % {'s': structName,
+ 'S': structName.upper(),
+ 'c': columnName,
+ 'C': columnName.upper()}
+
+ # Set functions.
+ for columnName, column in sorted(table.columns.iteritems()):
+ type = column.type
+ print '\nvoid'
+ members = cMembers(prefix, columnName, column, True)
+ keyVar = members[0]['name']
+ nVar = None
+ valueVar = None
+ if type.value:
+ valueVar = members[1]['name']
+ if len(members) > 2:
+ nVar = members[2]['name']
+ else:
+ if len(members) > 1:
+ nVar = members[1]['name']
+ print '%(s)s_set_%(c)s(const struct %(s)s *row, %(args)s)' % \
+ {'s': structName, 'c': columnName,
+ 'args': ', '.join(['%(type)s%(name)s' % m for m in members])}
+ print "{"
+ print " struct ovsdb_datum datum;"
+ if type.min == 1 and type.max == 1:
+ print
+ print " assert(inited);"
+ print " datum.n = 1;"
+ print " datum.keys = xmalloc(sizeof *datum.keys);"
+ print " " + type.key.copyCValue("datum.keys[0].%s" % type.key.type, keyVar)
+ if type.value:
+ print " datum.values = xmalloc(sizeof *datum.values);"
+ print " "+ type.value.copyCValue("datum.values[0].%s" % type.value.type, valueVar)
+ else:
+ print " datum.values = NULL;"
+ elif type.isOptionalPointer():
+ print
+ print " assert(inited);"
+ print " if (%s) {" % keyVar
+ print " datum.n = 1;"
+ print " datum.keys = xmalloc(sizeof *datum.keys);"
+ print " " + type.key.copyCValue("datum.keys[0].%s" % type.key.type, keyVar)
+ print " } else {"
+ print " datum.n = 0;"
+ print " datum.keys = NULL;"
+ print " }"
+ print " datum.values = NULL;"
+ else:
+ print " size_t i;"
+ print
+ print " assert(inited);"
+ print " datum.n = %s;" % nVar
+ print " datum.keys = xmalloc(%s * sizeof *datum.keys);" % nVar
+ if type.value:
+ print " datum.values = xmalloc(%s * sizeof *datum.values);" % nVar
+ else:
+ print " datum.values = NULL;"
+ print " for (i = 0; i < %s; i++) {" % nVar
+ print " " + type.key.copyCValue("datum.keys[i].%s" % type.key.type, "%s[i]" % keyVar)
+ if type.value:
+ print " " + type.value.copyCValue("datum.values[i].%s" % type.value.type, "%s[i]" % valueVar)
+ print " }"
+ print " ovsdb_idl_txn_write(&row->header_, &%(s)s_columns[%(S)s_COL_%(C)s], &datum);" \
+ % {'s': structName,
+ 'S': structName.upper(),
+ 'C': columnName.upper()}
+ print "}"
+
+ # Table columns.
+ print "\nstruct ovsdb_idl_column %s_columns[%s_N_COLUMNS];" % (
+ structName, structName.upper())
+ print """
+static void\n%s_columns_init(void)
+{
+ struct ovsdb_idl_column *c;\
+""" % structName
+ for columnName, column in sorted(table.columns.iteritems()):
+ cs = "%s_col_%s" % (structName, columnName)
+ d = {'cs': cs, 'c': columnName, 's': structName}
+ print
+ print " /* Initialize %(cs)s. */" % d
+ print " c = &%(cs)s;" % d
+ print " c->name = \"%(c)s\";" % d
+ print column.type.cInitType(" ", "c->type")
+ print " c->parse = %(s)s_parse_%(c)s;" % d
+ print " c->unparse = %(s)s_unparse_%(c)s;" % d
+ print "}"
+
+ # Table classes.
+ print "\f"
+ print "struct ovsdb_idl_table_class %stable_classes[%sN_TABLES] = {" % (prefix, prefix.upper())
+ for tableName, table in sorted(schema.tables.iteritems()):
+ structName = "%s%s" % (prefix, tableName.lower())
+ print " {\"%s\"," % tableName
+ print " %s_columns, ARRAY_SIZE(%s_columns)," % (
+ structName, structName)
+ print " sizeof(struct %s)}," % structName
+ print "};"
+
+ # IDL class.
+ print "\nstruct ovsdb_idl_class %sidl_class = {" % prefix
+ print " \"%s\", %stable_classes, ARRAY_SIZE(%stable_classes)" % (
+ schema.name, prefix, prefix)
+ print "};"
+
+ # global init function
+ print """
+void
+%sinit(void)
+{
+ if (inited) {
+ return;
+ }
+ inited = true;
+""" % prefix
+ for tableName, table in sorted(schema.tables.iteritems()):
+ structName = "%s%s" % (prefix, tableName.lower())
+ print " %s_columns_init();" % structName
+ print "}"
+
+def ovsdb_escape(string):
+ def escape(match):
+ c = match.group(0)
+ if c == '\0':
+ raise Error("strings may not contain null bytes")
+ elif c == '\\':
+ return '\\\\'
+ elif c == '\n':
+ return '\\n'
+ elif c == '\r':
+ return '\\r'
+ elif c == '\t':
+ return '\\t'
+ elif c == '\b':
+ return '\\b'
+ elif c == '\a':
+ return '\\a'
+ else:
+ return '\\x%02x' % ord(c)
+ return re.sub(r'["\\\000-\037]', escape, string)
+
+def printDoc(schemaFile):
+ schema = parseSchema(schemaFile)
+ print schema.name
+ if schema.comment:
+ print schema.comment
+
+ for tableName, table in sorted(schema.tables.iteritems()):
+ title = "%s table" % tableName
+ print
+ print title
+ print '-' * len(title)
+ if table.comment:
+ print table.comment
+
+ for columnName, column in sorted(table.columns.iteritems()):
+ print
+ print "%s (%s)" % (columnName, column.type.toEnglish())
+ if column.comment:
+ print "\t%s" % column.comment
+
+def usage():
+ print """\
+%(argv0)s: ovsdb schema compiler
+usage: %(argv0)s [OPTIONS] COMMAND ARG...
+
+The following commands are supported:
+ annotate SCHEMA ANNOTATIONS print SCHEMA combined with ANNOTATIONS
+ c-idl-header IDL print C header file for IDL
+ c-idl-source IDL print C source file for IDL implementation
+ doc IDL print schema documentation
+
+The following options are also available:
+ -h, --help display this help message
+ -V, --version display version information\
+""" % {'argv0': argv0}
+ sys.exit(0)
+
+if __name__ == "__main__":
+ try:
+ try:
+ options, args = getopt.gnu_getopt(sys.argv[1:], 'C:hV',
+ ['directory',
+ 'help',
+ 'version'])
+ except getopt.GetoptError, geo:
+ sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
+ sys.exit(1)
+
+ for key, value in options:
+ if key in ['-h', '--help']:
+ usage()
+ elif key in ['-V', '--version']:
+ print "ovsdb-idlc (Open vSwitch) @VERSION@"
+ elif key in ['-C', '--directory']:
+ os.chdir(value)
+ else:
+ sys.exit(0)
+
+ optKeys = [key for key, value in options]
+
+ if not args:
+ sys.stderr.write("%s: missing command argument "
+ "(use --help for help)\n" % argv0)
+ sys.exit(1)
+
+ commands = {"annotate": (annotateSchema, 2),
+ "c-idl-header": (printCIDLHeader, 1),
+ "c-idl-source": (printCIDLSource, 1),
+ "doc": (printDoc, 1)}
+
+ if not args[0] in commands:
+ sys.stderr.write("%s: unknown command \"%s\" "
+ "(use --help for help)\n" % (argv0, args[0]))
+ sys.exit(1)
+
+ func, n_args = commands[args[0]]
+ if len(args) - 1 != n_args:
+ sys.stderr.write("%s: \"%s\" requires %d arguments but %d "
+ "provided\n"
+ % (argv0, args[0], n_args, len(args) - 1))
+ sys.exit(1)
+
+ func(*args[1:])
+ except Error, e:
+ sys.stderr.write("%s: %s\n" % (argv0, e.msg))
+ sys.exit(1)
+
+# Local variables:
+# mode: python
+# End:
--- /dev/null
- ovsdb_server_exit(struct unixctl_conn *conn, const char *args UNUSED,
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "column.h"
+#include "command-line.h"
+#include "daemon.h"
+#include "file.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "jsonrpc-server.h"
+#include "leak-checker.h"
+#include "list.h"
+#include "ovsdb-data.h"
+#include "ovsdb-types.h"
+#include "ovsdb-error.h"
+#include "poll-loop.h"
+#include "process.h"
+#include "row.h"
+#include "stream-ssl.h"
+#include "stream.h"
+#include "svec.h"
+#include "table.h"
+#include "timeval.h"
+#include "trigger.h"
+#include "util.h"
+#include "unixctl.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_server
+
+static unixctl_cb_func ovsdb_server_exit;
+
+static void parse_options(int argc, char *argv[], char **file_namep,
+ struct shash *remotes, char **unixctl_pathp);
+static void usage(void) NO_RETURN;
+
+static void set_remotes(struct ovsdb_jsonrpc_server *jsonrpc,
+ const struct ovsdb *db, struct shash *remotes);
+
+int
+main(int argc, char *argv[])
+{
+ char *unixctl_path = NULL;
+ struct unixctl_server *unixctl;
+ struct ovsdb_jsonrpc_server *jsonrpc;
+ struct shash remotes;
+ struct ovsdb_error *error;
+ struct ovsdb *db;
+ char *file_name;
+ bool exiting;
+ int retval;
+
+ proctitle_init(argc, argv);
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+ signal(SIGPIPE, SIG_IGN);
+ process_init();
+
+ parse_options(argc, argv, &file_name, &remotes, &unixctl_path);
+
+ die_if_already_running();
+ daemonize_start();
+
+ error = ovsdb_file_open(file_name, false, &db);
+ if (error) {
+ ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+ }
+
+ jsonrpc = ovsdb_jsonrpc_server_create(db);
+ set_remotes(jsonrpc, db, &remotes);
+
+ retval = unixctl_server_create(unixctl_path, &unixctl);
+ if (retval) {
+ exit(EXIT_FAILURE);
+ }
+
+ daemonize_complete();
+
+ unixctl_command_register("exit", ovsdb_server_exit, &exiting);
+
+ exiting = false;
+ while (!exiting) {
+ set_remotes(jsonrpc, db, &remotes);
+ ovsdb_jsonrpc_server_run(jsonrpc);
+ unixctl_server_run(unixctl);
+ ovsdb_trigger_run(db, time_msec());
+
+ ovsdb_jsonrpc_server_wait(jsonrpc);
+ unixctl_server_wait(unixctl);
+ ovsdb_trigger_wait(db, time_msec());
+ poll_block();
+ }
+ ovsdb_jsonrpc_server_destroy(jsonrpc);
+ ovsdb_destroy(db);
+ shash_destroy(&remotes);
+ unixctl_server_destroy(unixctl);
+
+ return 0;
+}
+
+static void
+query_db_remotes(const char *name_, const struct ovsdb *db,
+ struct shash *remotes)
+{
+ char *name, *table_name, *column_name;
+ const struct ovsdb_column *column;
+ const struct ovsdb_table *table;
+ const struct ovsdb_row *row;
+ char *save_ptr = NULL;
+
+ name = xstrdup(name_);
+ strtok_r(name, ":", &save_ptr); /* "db:" */
+ table_name = strtok_r(NULL, ",", &save_ptr);
+ column_name = strtok_r(NULL, ",", &save_ptr);
+ if (!table_name || !column_name) {
+ ovs_fatal(0, "remote \"%s\": invalid syntax", name_);
+ }
+
+ table = ovsdb_get_table(db, table_name);
+ if (!table) {
+ ovs_fatal(0, "remote \"%s\": no table named %s", name_, table_name);
+ }
+
+ column = ovsdb_table_schema_get_column(table->schema, column_name);
+ if (!column) {
+ ovs_fatal(0, "remote \"%s\": table \"%s\" has no column \"%s\"",
+ name_, table_name, column_name);
+ }
+
+ if (column->type.key.type != OVSDB_TYPE_STRING
+ || column->type.value.type != OVSDB_TYPE_VOID) {
+ ovs_fatal(0, "remote \"%s\": type of table \"%s\" column \"%s\" is "
+ "not string or set of strings",
+ name_, table_name, column_name);
+ }
+
+ HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node, &table->rows) {
+ const struct ovsdb_datum *datum;
+ size_t i;
+
+ datum = &row->fields[column->index];
+ for (i = 0; i < datum->n; i++) {
+ shash_add_once(remotes, datum->keys[i].string, NULL);
+ }
+ }
+
+ free(name);
+}
+
+static void
+set_remotes(struct ovsdb_jsonrpc_server *jsonrpc,
+ const struct ovsdb *db, struct shash *remotes)
+{
+ struct shash resolved_remotes;
+ struct shash_node *node;
+
+ shash_init(&resolved_remotes);
+ SHASH_FOR_EACH (node, remotes) {
+ const char *name = node->name;
+
+ if (!strncmp(name, "db:", 3)) {
+ query_db_remotes(name, db, &resolved_remotes);
+ } else {
+ shash_add_once(&resolved_remotes, name, NULL);
+ }
+ }
+ ovsdb_jsonrpc_server_set_remotes(jsonrpc, &resolved_remotes);
+ shash_destroy(&resolved_remotes);
+}
+
+
+static void
++ovsdb_server_exit(struct unixctl_conn *conn, const char *args OVS_UNUSED,
+ void *exiting_)
+{
+ bool *exiting = exiting_;
+ *exiting = true;
+ unixctl_command_reply(conn, 200, NULL);
+}
+
+static void
+parse_options(int argc, char *argv[], char **file_namep,
+ struct shash *remotes, char **unixctl_pathp)
+{
+ enum {
+ OPT_DUMMY = UCHAR_MAX + 1,
+ OPT_REMOTE,
+ OPT_UNIXCTL,
+ OPT_BOOTSTRAP_CA_CERT,
+ VLOG_OPTION_ENUMS,
+ LEAK_CHECKER_OPTION_ENUMS
+ };
+ static struct option long_options[] = {
+ {"remote", required_argument, 0, OPT_REMOTE},
+ {"unixctl", required_argument, 0, OPT_UNIXCTL},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ DAEMON_LONG_OPTIONS,
+ VLOG_LONG_OPTIONS,
+ LEAK_CHECKER_LONG_OPTIONS,
+#ifdef HAVE_OPENSSL
+ {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT},
+ STREAM_SSL_LONG_OPTIONS
+#endif
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+
+ shash_init(remotes);
+ for (;;) {
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case OPT_REMOTE:
+ shash_add_once(remotes, optarg, NULL);
+ break;
+
+ case OPT_UNIXCTL:
+ *unixctl_pathp = optarg;
+ break;
+
+ case 'h':
+ usage();
+
+ case 'V':
+ OVS_PRINT_VERSION(0, 0);
+ exit(EXIT_SUCCESS);
+
+ VLOG_OPTION_HANDLERS
+ DAEMON_OPTION_HANDLERS
+ LEAK_CHECKER_OPTION_HANDLERS
+
+#ifdef HAVE_OPENSSL
+ STREAM_SSL_OPTION_HANDLERS
+
+ case OPT_BOOTSTRAP_CA_CERT:
+ stream_ssl_set_ca_cert_file(optarg, true);
+ break;
+#endif
+
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+ free(short_options);
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 1) {
+ ovs_fatal(0, "database file is only non-option argument; "
+ "use --help for usage");
+ } else if (argc < 1) {
+ ovs_fatal(0, "missing database file argument; use --help for usage");
+ }
+
+ *file_namep = argv[0];
+}
+
+static void
+usage(void)
+{
+ printf("%s: Open vSwitch database server\n"
+ "usage: %s [OPTIONS] DATABASE\n"
+ "where DATABASE is a database file in ovsdb format.\n",
+ program_name, program_name);
+ printf("\nJSON-RPC options (may be specified any number of times):\n"
+ " --remote=REMOTE connect or listen to REMOTE\n");
+ stream_usage("JSON-RPC", true, true, true);
+ daemon_usage();
+ vlog_usage();
+ printf("\nOther options:\n"
+ " -h, --help display this help message\n"
+ " -V, --version display version information\n");
+ leak_checker_usage();
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
- do_create(int argc UNUSED, char *argv[])
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "file.h"
+#include "log.h"
+#include "json.h"
+#include "ovsdb.h"
+#include "ovsdb-error.h"
+#include "table.h"
+#include "timeval.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_tool
+
+/* -m, --more: Verbosity level for "show-log" command output. */
+static int show_log_verbosity;
+
+static const struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+ parse_options(argc, argv);
+ signal(SIGPIPE, SIG_IGN);
+ run_command(argc - optind, argv + optind, all_commands);
+ return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+ static struct option long_options[] = {
+ {"more", no_argument, 0, 'm'},
+ {"verbose", optional_argument, 0, 'v'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+
+ for (;;) {
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'm':
+ show_log_verbosity++;
+ break;
+
+ case 'h':
+ usage();
+
+ case 'V':
+ OVS_PRINT_VERSION(0, 0);
+ exit(EXIT_SUCCESS);
+
+ case 'v':
+ vlog_set_verbosity(optarg);
+ break;
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+ free(short_options);
+}
+
+static void
+usage(void)
+{
+ printf("%s: Open vSwitch database management utility\n"
+ "usage: %s [OPTIONS] COMMAND [ARG...]\n"
+ " create DB SCHEMA create DB with the given SCHEMA\n"
+ " compact DB [DST] compact DB in-place (or to DST)\n"
+ " extract-schema DB print DB's schema on stdout\n"
+ " query DB TRNS execute read-only transaction on DB\n"
+ " transact DB TRNS execute read/write transaction on DB\n"
+ " show-log DB prints information about DB's log entries\n",
+ program_name, program_name);
+ vlog_usage();
+ printf("\nOther options:\n"
+ " -m, --more increase show-log verbosity\n"
+ " -h, --help display this help message\n"
+ " -V, --version display version information\n");
+ exit(EXIT_SUCCESS);
+}
+\f
+static struct json *
+parse_json(const char *s)
+{
+ struct json *json = json_from_string(s);
+ if (json->type == JSON_STRING) {
+ ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+ }
+ return json;
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+ char *string = json_to_string(json, JSSF_SORT);
+ json_destroy(json);
+ puts(string);
+ free(string);
+}
+
+static void
+check_ovsdb_error(struct ovsdb_error *error)
+{
+ if (error) {
+ ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+ }
+}
+\f
+static void
- do_query(int argc UNUSED, char *argv[])
++do_create(int argc OVS_UNUSED, char *argv[])
+{
+ const char *db_file_name = argv[1];
+ const char *schema_file_name = argv[2];
+ struct ovsdb_schema *schema;
+ struct ovsdb_log *log;
+ struct json *json;
+
+ /* Read schema from file and convert to JSON. */
+ check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema));
+ json = ovsdb_schema_to_json(schema);
+ ovsdb_schema_destroy(schema);
+
+ /* Create database file. */
+ check_ovsdb_error(ovsdb_log_open(db_file_name, O_RDWR | O_CREAT | O_EXCL,
+ &log));
+ check_ovsdb_error(ovsdb_log_write(log, json));
+ check_ovsdb_error(ovsdb_log_commit(log));
+ ovsdb_log_close(log);
+
+ json_destroy(json);
+}
+
+static void
+transact(bool read_only, const char *db_file_name, const char *transaction)
+{
+ struct json *request, *result;
+ struct ovsdb *db;
+
+ check_ovsdb_error(ovsdb_file_open(db_file_name, read_only, &db));
+
+ request = parse_json(transaction);
+ result = ovsdb_execute(db, request, 0, NULL);
+ json_destroy(request);
+
+ print_and_free_json(result);
+ ovsdb_destroy(db);
+}
+
+static void
- do_transact(int argc UNUSED, char *argv[])
++do_query(int argc OVS_UNUSED, char *argv[])
+{
+ transact(true, argv[1], argv[2]);
+}
+
+static void
- do_show_log(int argc UNUSED, char *argv[])
++do_transact(int argc OVS_UNUSED, char *argv[])
+{
+ transact(false, argv[1], argv[2]);
+}
+
+static void
+print_db_changes(struct shash *tables, struct shash *names)
+{
+ struct shash_node *n1;
+
+ SHASH_FOR_EACH (n1, tables) {
+ const char *table = n1->name;
+ struct json *rows = n1->data;
+ struct shash_node *n2;
+
+ if (n1->name[0] == '_' || rows->type != JSON_OBJECT) {
+ continue;
+ }
+
+ SHASH_FOR_EACH (n2, json_object(rows)) {
+ const char *row_uuid = n2->name;
+ struct json *columns = n2->data;
+ struct shash_node *n3;
+ char *old_name, *new_name;
+ bool free_new_name = false;
+
+ old_name = new_name = shash_find_data(names, row_uuid);
+ if (columns->type == JSON_OBJECT) {
+ struct json *new_name_json;
+
+ new_name_json = shash_find_data(json_object(columns), "name");
+ if (new_name_json) {
+ new_name = json_to_string(new_name_json, JSSF_SORT);
+ free_new_name = true;
+ }
+ }
+
+ printf("\ttable %s", table);
+
+ if (!old_name) {
+ if (new_name) {
+ printf(" insert row %s:\n", new_name);
+ } else {
+ printf(" insert row %.8s:\n", row_uuid);
+ }
+ } else {
+ printf(" row %s:\n", old_name);
+ }
+
+ if (columns->type == JSON_OBJECT) {
+ if (show_log_verbosity > 1) {
+ SHASH_FOR_EACH (n3, json_object(columns)) {
+ const char *column = n3->name;
+ struct json *value = n3->data;
+ char *value_string;
+
+ value_string = json_to_string(value, JSSF_SORT);
+ printf("\t\t%s=%s\n", column, value_string);
+ free(value_string);
+ }
+ }
+ if (!old_name
+ || (new_name != old_name && strcmp(old_name, new_name))) {
+ if (old_name) {
+ shash_delete(names, shash_find(names, row_uuid));
+ free(old_name);
+ }
+ shash_add(names, row_uuid, (new_name
+ ? xstrdup(new_name)
+ : xmemdup0(row_uuid, 8)));
+ }
+ } else if (columns->type == JSON_NULL) {
+ printf("\t\tdelete row\n");
+ shash_delete(names, shash_find(names, row_uuid));
+ free(old_name);
+ }
+
+ if (free_new_name) {
+ free(new_name);
+ }
+ }
+ }
+}
+
+static void
- do_help(int argc UNUSED, char *argv[] UNUSED)
++do_show_log(int argc OVS_UNUSED, char *argv[])
+{
+ const char *db_file_name = argv[1];
+ struct shash names;
+ struct ovsdb_log *log;
+ unsigned int i;
+
+ check_ovsdb_error(ovsdb_log_open(db_file_name, O_RDONLY, &log));
+ shash_init(&names);
+ for (i = 0; ; i++) {
+ struct json *json;
+
+ check_ovsdb_error(ovsdb_log_read(log, &json));
+ if (!json) {
+ break;
+ }
+
+ printf("record %u:", i);
+ if (json->type == JSON_OBJECT) {
+ struct json *date, *comment;
+
+ date = shash_find_data(json_object(json), "_date");
+ if (date && date->type == JSON_INTEGER) {
+ time_t t = json_integer(date);
+ char s[128];
+
+ strftime(s, sizeof s, "%Y-%m-%d %H:%M:%S", localtime(&t));
+ printf(" %s", s);
+ }
+
+ comment = shash_find_data(json_object(json), "_comment");
+ if (comment && comment->type == JSON_STRING) {
+ printf(" \"%s\"", json_string(comment));
+ }
+
+ if (i > 0 && show_log_verbosity > 0) {
+ putchar('\n');
+ print_db_changes(json_object(json), &names);
+ }
+ }
+ json_destroy(json);
+ putchar('\n');
+ }
+
+ /* XXX free 'names'. */
+}
+
+static void
++do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ usage();
+}
+
+static const struct command all_commands[] = {
+ { "create", 2, 2, do_create },
+ { "query", 2, 2, do_query },
+ { "transact", 2, 2, do_transact },
+ { "show-log", 1, 1, do_show_log },
+ { "help", 0, INT_MAX, do_help },
+ { NULL, 0, 0, NULL },
+};
--- /dev/null
- ovsdb_remove_replica(struct ovsdb *db UNUSED, struct ovsdb_replica *r)
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb.h"
+
+#include "column.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb-types.h"
+#include "table.h"
+#include "transaction.h"
+
+struct ovsdb_schema *
+ovsdb_schema_create(const char *name, const char *comment)
+{
+ struct ovsdb_schema *schema;
+
+ schema = xzalloc(sizeof *schema);
+ schema->name = xstrdup(name);
+ schema->comment = comment ? xstrdup(comment) : NULL;
+ shash_init(&schema->tables);
+
+ return schema;
+}
+
+void
+ovsdb_schema_destroy(struct ovsdb_schema *schema)
+{
+ struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &schema->tables) {
+ ovsdb_table_schema_destroy(node->data);
+ }
+ shash_destroy(&schema->tables);
+ free(schema->comment);
+ free(schema->name);
+ free(schema);
+}
+
+struct ovsdb_error *
+ovsdb_schema_from_file(const char *file_name, struct ovsdb_schema **schemap)
+{
+ struct ovsdb_schema *schema;
+ struct ovsdb_error *error;
+ struct json *json;
+
+ *schemap = NULL;
+ json = json_from_file(file_name);
+ if (json->type == JSON_STRING) {
+ error = ovsdb_error("failed to read schema",
+ "\"%s\" could not be read as JSON (%s)",
+ file_name, json_string(json));
+ json_destroy(json);
+ return error;
+ }
+
+ error = ovsdb_schema_from_json(json, &schema);
+ json_destroy(json);
+ if (error) {
+ return ovsdb_wrap_error(error,
+ "failed to parse \"%s\" as ovsdb schema",
+ file_name);
+ }
+
+ *schemap = schema;
+ return NULL;
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+ovsdb_schema_check_ref_table(const struct ovsdb_column *column,
+ const struct shash *tables,
+ const struct ovsdb_base_type *base,
+ const char *base_name)
+{
+ if (base->type == OVSDB_TYPE_UUID && base->u.uuid.refTableName
+ && !shash_find(tables, base->u.uuid.refTableName)) {
+ return ovsdb_syntax_error(NULL, NULL,
+ "column %s %s refers to undefined table %s",
+ column->name, base_name,
+ base->u.uuid.refTableName);
+ } else {
+ return NULL;
+ }
+}
+
+struct ovsdb_error *
+ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap)
+{
+ struct ovsdb_schema *schema;
+ const struct json *name, *comment, *tables;
+ struct ovsdb_error *error;
+ struct shash_node *node;
+ struct ovsdb_parser parser;
+
+ *schemap = NULL;
+
+ ovsdb_parser_init(&parser, json, "database schema");
+ name = ovsdb_parser_member(&parser, "name", OP_ID);
+ comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL);
+ tables = ovsdb_parser_member(&parser, "tables", OP_OBJECT);
+ error = ovsdb_parser_finish(&parser);
+ if (error) {
+ return error;
+ }
+
+ schema = ovsdb_schema_create(json_string(name),
+ comment ? json_string(comment) : NULL);
+ SHASH_FOR_EACH (node, json_object(tables)) {
+ struct ovsdb_table_schema *table;
+
+ if (node->name[0] == '_') {
+ error = ovsdb_syntax_error(json, NULL, "names beginning with "
+ "\"_\" are reserved");
+ } else if (!ovsdb_parser_is_id(node->name)) {
+ error = ovsdb_syntax_error(json, NULL, "name must be a valid id");
+ } else {
+ error = ovsdb_table_schema_from_json(node->data, node->name,
+ &table);
+ }
+ if (error) {
+ ovsdb_schema_destroy(schema);
+ return error;
+ }
+
+ shash_add(&schema->tables, table->name, table);
+ }
+
+ /* Validate that all refTables refer to the names of tables that exist. */
+ SHASH_FOR_EACH (node, &schema->tables) {
+ struct ovsdb_table_schema *table = node->data;
+ struct shash_node *node2;
+
+ SHASH_FOR_EACH (node2, &table->columns) {
+ struct ovsdb_column *column = node2->data;
+
+ error = ovsdb_schema_check_ref_table(column, &schema->tables,
+ &column->type.key, "key");
+ if (!error) {
+ error = ovsdb_schema_check_ref_table(column, &schema->tables,
+ &column->type.value,
+ "value");
+ }
+ if (error) {
+ ovsdb_schema_destroy(schema);
+ return error;
+ }
+ }
+ }
+
+ *schemap = schema;
+ return 0;
+}
+
+struct json *
+ovsdb_schema_to_json(const struct ovsdb_schema *schema)
+{
+ struct json *json, *tables;
+ struct shash_node *node;
+
+ json = json_object_create();
+ json_object_put_string(json, "name", schema->name);
+ if (schema->comment) {
+ json_object_put_string(json, "comment", schema->comment);
+ }
+
+ tables = json_object_create();
+
+ SHASH_FOR_EACH (node, &schema->tables) {
+ struct ovsdb_table_schema *table = node->data;
+ json_object_put(tables, table->name,
+ ovsdb_table_schema_to_json(table));
+ }
+ json_object_put(json, "tables", tables);
+
+ return json;
+}
+\f
+static void
+ovsdb_set_ref_table(const struct shash *tables,
+ struct ovsdb_base_type *base)
+{
+ if (base->type == OVSDB_TYPE_UUID && base->u.uuid.refTableName) {
+ struct ovsdb_table *table;
+
+ table = shash_find_data(tables, base->u.uuid.refTableName);
+ base->u.uuid.refTable = table;
+ }
+}
+
+struct ovsdb *
+ovsdb_create(struct ovsdb_schema *schema)
+{
+ struct shash_node *node;
+ struct ovsdb *db;
+
+ db = xmalloc(sizeof *db);
+ db->schema = schema;
+ list_init(&db->replicas);
+ list_init(&db->triggers);
+ db->run_triggers = false;
+
+ shash_init(&db->tables);
+ SHASH_FOR_EACH (node, &schema->tables) {
+ struct ovsdb_table_schema *ts = node->data;
+ shash_add(&db->tables, node->name, ovsdb_table_create(ts));
+ }
+
+ /* Set all the refTables. */
+ SHASH_FOR_EACH (node, &schema->tables) {
+ struct ovsdb_table_schema *table = node->data;
+ struct shash_node *node2;
+
+ SHASH_FOR_EACH (node2, &table->columns) {
+ struct ovsdb_column *column = node2->data;
+
+ ovsdb_set_ref_table(&db->tables, &column->type.key);
+ ovsdb_set_ref_table(&db->tables, &column->type.value);
+ }
+ }
+
+ return db;
+}
+
+void
+ovsdb_destroy(struct ovsdb *db)
+{
+ if (db) {
+ struct shash_node *node;
+
+ /* Remove all the replicas. */
+ while (!list_is_empty(&db->replicas)) {
+ struct ovsdb_replica *r
+ = CONTAINER_OF(list_pop_back(&db->replicas),
+ struct ovsdb_replica, node);
+ ovsdb_remove_replica(db, r);
+ }
+
+ /* Delete all the tables. This also deletes their schemas. */
+ SHASH_FOR_EACH (node, &db->tables) {
+ struct ovsdb_table *table = node->data;
+ ovsdb_table_destroy(table);
+ }
+ shash_destroy(&db->tables);
+
+ /* The schemas, but not the table that points to them, were deleted in
+ * the previous step, so we need to clear out the table. We can't
+ * destroy the table, because ovsdb_schema_destroy() will do that. */
+ shash_clear(&db->schema->tables);
+
+ ovsdb_schema_destroy(db->schema);
+ free(db);
+ }
+}
+
+struct ovsdb_table *
+ovsdb_get_table(const struct ovsdb *db, const char *name)
+{
+ return shash_find_data(&db->tables, name);
+}
+\f
+void
+ovsdb_replica_init(struct ovsdb_replica *r,
+ const struct ovsdb_replica_class *class)
+{
+ r->class = class;
+}
+
+void
+ovsdb_add_replica(struct ovsdb *db, struct ovsdb_replica *r)
+{
+ list_push_back(&db->replicas, &r->node);
+}
+
+void
++ovsdb_remove_replica(struct ovsdb *db OVS_UNUSED, struct ovsdb_replica *r)
+{
+ list_remove(&r->node);
+ (r->class->destroy)(r);
+}
--- /dev/null
- do_listen(int argc UNUSED, char *argv[])
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "jsonrpc.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "command-line.h"
+#include "daemon.h"
+#include "json.h"
+#include "poll-loop.h"
+#include "stream-ssl.h"
+#include "stream.h"
+#include "timeval.h"
+#include "util.h"
+#include "vlog.h"
+
+static struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+ proctitle_init(argc, argv);
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+ parse_options(argc, argv);
+ run_command(argc - optind, argv + optind, all_commands);
+ return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+ enum {
+ OPT_BOOTSTRAP_CA_CERT = UCHAR_MAX + 1
+ };
+ static struct option long_options[] = {
+ {"verbose", optional_argument, 0, 'v'},
+ {"help", no_argument, 0, 'h'},
+ DAEMON_LONG_OPTIONS,
+#ifdef HAVE_OPENSSL
+ {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT},
+ STREAM_SSL_LONG_OPTIONS
+#endif
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+
+ for (;;) {
+ int c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'h':
+ usage();
+
+ case 'v':
+ vlog_set_verbosity(optarg);
+ break;
+
+ DAEMON_OPTION_HANDLERS
+
+#ifdef HAVE_OPENSSL
+ STREAM_SSL_OPTION_HANDLERS
+
+ case OPT_BOOTSTRAP_CA_CERT:
+ stream_ssl_set_ca_cert_file(optarg, true);
+ break;
+#endif
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+ free(short_options);
+}
+
+static void
+usage(void)
+{
+ printf("%s: JSON-RPC test utility\n"
+ "usage: %s [OPTIONS] COMMAND [ARG...]\n"
+ " listen LOCAL listen for connections on LOCAL\n"
+ " request REMOTE METHOD PARAMS send request, print reply\n"
+ " notify REMOTE METHOD PARAMS send notification and exit\n",
+ program_name, program_name);
+ stream_usage("JSON-RPC", true, true, true);
+ daemon_usage();
+ vlog_usage();
+ printf("\nOther options:\n"
+ " -h, --help display this help message\n");
+ exit(EXIT_SUCCESS);
+}
+\f
+/* Command helper functions. */
+
+static struct json *
+parse_json(const char *s)
+{
+ struct json *json = json_from_string(s);
+ if (json->type == JSON_STRING) {
+ ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+ }
+ return json;
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+ char *string = json_to_string(json, JSSF_SORT);
+ json_destroy(json);
+ puts(string);
+ free(string);
+}
+\f
+/* Command implementations. */
+
+static void
+handle_rpc(struct jsonrpc *rpc, struct jsonrpc_msg *msg, bool *done)
+{
+ struct jsonrpc_msg *reply = NULL;
+ if (msg->type == JSONRPC_REQUEST) {
+ if (!strcmp(msg->method, "echo")) {
+ reply = jsonrpc_create_reply(json_clone(msg->params), msg->id);
+ } else {
+ struct json *error = json_object_create();
+ json_object_put_string(error, "error", "unknown method");
+ reply = jsonrpc_create_error(error, msg->id);
+ ovs_error(0, "unknown request %s", msg->method);
+ }
+
+ } else if (msg->type == JSONRPC_NOTIFY) {
+ if (!strcmp(msg->method, "shutdown")) {
+ *done = true;
+ } else {
+ jsonrpc_error(rpc, ENOTTY);
+ ovs_error(0, "unknown notification %s", msg->method);
+ }
+ } else {
+ jsonrpc_error(rpc, EPROTO);
+ ovs_error(0, "unsolicited JSON-RPC reply or error");
+ }
+
+ if (reply) {
+ jsonrpc_send(rpc, reply);
+ }
+}
+
+static void
- do_request(int argc UNUSED, char *argv[])
++do_listen(int argc OVS_UNUSED, char *argv[])
+{
+ struct pstream *pstream;
+ struct jsonrpc **rpcs;
+ size_t n_rpcs, allocated_rpcs;
+ bool done;
+ int error;
+
+ die_if_already_running();
+
+ error = pstream_open(argv[1], &pstream);
+ if (error) {
+ ovs_fatal(error, "could not listen on \"%s\"", argv[1]);
+ }
+
+ daemonize();
+
+ rpcs = NULL;
+ n_rpcs = allocated_rpcs = 0;
+ done = false;
+ for (;;) {
+ struct stream *stream;
+ size_t i;
+
+ /* Accept new connections. */
+ error = pstream_accept(pstream, &stream);
+ if (!error) {
+ if (n_rpcs >= allocated_rpcs) {
+ rpcs = x2nrealloc(rpcs, &allocated_rpcs, sizeof *rpcs);
+ }
+ rpcs[n_rpcs++] = jsonrpc_open(stream);
+ } else if (error != EAGAIN) {
+ ovs_fatal(error, "pstream_accept failed");
+ }
+
+ /* Service existing connections. */
+ for (i = 0; i < n_rpcs; ) {
+ struct jsonrpc *rpc = rpcs[i];
+ struct jsonrpc_msg *msg;
+
+ jsonrpc_run(rpc);
+ if (!jsonrpc_get_backlog(rpc)) {
+ error = jsonrpc_recv(rpc, &msg);
+ if (!error) {
+ handle_rpc(rpc, msg, &done);
+ jsonrpc_msg_destroy(msg);
+ }
+ }
+
+ error = jsonrpc_get_status(rpc);
+ if (error) {
+ jsonrpc_close(rpc);
+ ovs_error(error, "connection closed");
+ memmove(&rpcs[i], &rpcs[i + 1],
+ (n_rpcs - i - 1) * sizeof *rpcs);
+ n_rpcs--;
+ } else {
+ i++;
+ }
+ }
+
+ /* Wait for something to do. */
+ if (done && !n_rpcs) {
+ break;
+ }
+ pstream_wait(pstream);
+ for (i = 0; i < n_rpcs; i++) {
+ struct jsonrpc *rpc = rpcs[i];
+
+ jsonrpc_wait(rpc);
+ if (!jsonrpc_get_backlog(rpc)) {
+ jsonrpc_recv_wait(rpc);
+ }
+ }
+ poll_block();
+ }
+ free(rpcs);
+ pstream_close(pstream);
+}
+
+static void
- do_notify(int argc UNUSED, char *argv[])
++do_request(int argc OVS_UNUSED, char *argv[])
+{
+ struct jsonrpc_msg *msg;
+ struct jsonrpc *rpc;
+ struct json *params;
+ struct stream *stream;
+ const char *method;
+ char *string;
+ int error;
+
+ method = argv[2];
+ params = parse_json(argv[3]);
+ msg = jsonrpc_create_request(method, params, NULL);
+ string = jsonrpc_msg_is_valid(msg);
+ if (string) {
+ ovs_fatal(0, "not a valid JSON-RPC request: %s", string);
+ }
+
+ error = stream_open_block(argv[1], &stream);
+ if (error) {
+ ovs_fatal(error, "could not open \"%s\"", argv[1]);
+ }
+ rpc = jsonrpc_open(stream);
+
+ error = jsonrpc_send(rpc, msg);
+ if (error) {
+ ovs_fatal(error, "could not send request");
+ }
+
+ error = jsonrpc_recv_block(rpc, &msg);
+ if (error) {
+ ovs_fatal(error, "error waiting for reply");
+ }
+ print_and_free_json(jsonrpc_msg_to_json(msg));
+
+ jsonrpc_close(rpc);
+}
+
+static void
- do_help(int argc UNUSED, char *argv[] UNUSED)
++do_notify(int argc OVS_UNUSED, char *argv[])
+{
+ struct jsonrpc_msg *msg;
+ struct jsonrpc *rpc;
+ struct json *params;
+ struct stream *stream;
+ const char *method;
+ char *string;
+ int error;
+
+ method = argv[2];
+ params = parse_json(argv[3]);
+ msg = jsonrpc_create_notify(method, params);
+ string = jsonrpc_msg_is_valid(msg);
+ if (string) {
+ ovs_fatal(0, "not a JSON RPC-valid notification: %s", string);
+ }
+
+ error = stream_open_block(argv[1], &stream);
+ if (error) {
+ ovs_fatal(error, "could not open \"%s\"", argv[1]);
+ }
+ rpc = jsonrpc_open(stream);
+
+ error = jsonrpc_send_block(rpc, msg);
+ if (error) {
+ ovs_fatal(error, "could not send request");
+ }
+ jsonrpc_close(rpc);
+}
+
+static void
++do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ usage();
+}
+
+static struct command all_commands[] = {
+ { "listen", 1, 1, do_listen },
+ { "request", 3, 3, do_request },
+ { "notify", 3, 3, do_notify },
+ { "help", 0, INT_MAX, do_help },
+ { NULL, 0, 0, NULL },
+};
--- /dev/null
- do_parse_atomic_type(int argc UNUSED, char *argv[])
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "command-line.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-idl.h"
+#include "ovsdb-types.h"
+#include "ovsdb/column.h"
+#include "ovsdb/condition.h"
+#include "ovsdb/file.h"
+#include "ovsdb/log.h"
+#include "ovsdb/mutation.h"
+#include "ovsdb/ovsdb.h"
+#include "ovsdb/query.h"
+#include "ovsdb/row.h"
+#include "ovsdb/table.h"
+#include "ovsdb/transaction.h"
+#include "ovsdb/trigger.h"
+#include "poll-loop.h"
+#include "stream.h"
+#include "svec.h"
+#include "tests/idltest.h"
+#include "timeval.h"
+#include "util.h"
+#include "vlog.h"
+
+static struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+ parse_options(argc, argv);
+ run_command(argc - optind, argv + optind, all_commands);
+ return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+ static struct option long_options[] = {
+ {"timeout", required_argument, 0, 't'},
+ {"verbose", optional_argument, 0, 'v'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+
+ for (;;) {
+ unsigned long int timeout;
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 't':
+ timeout = strtoul(optarg, NULL, 10);
+ if (timeout <= 0) {
+ ovs_fatal(0, "value %s on -t or --timeout is not at least 1",
+ optarg);
+ } else {
+ time_alarm(timeout);
+ }
+ break;
+
+ case 'h':
+ usage();
+
+ case 'v':
+ vlog_set_verbosity(optarg);
+ break;
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+ free(short_options);
+}
+
+static void
+usage(void)
+{
+ printf("%s: Open vSwitch database test utility\n"
+ "usage: %s [OPTIONS] COMMAND [ARG...]\n\n"
+ " log-io FILE FLAGS COMMAND...\n"
+ " open FILE with FLAGS, run COMMANDs\n"
+ " parse-atomic-type TYPE\n"
+ " parse TYPE as OVSDB atomic type, and re-serialize\n"
+ " parse-base-type TYPE\n"
+ " parse TYPE as OVSDB base type, and re-serialize\n"
+ " parse-type JSON\n"
+ " parse JSON as OVSDB type, and re-serialize\n"
+ " parse-atoms TYPE ATOM...\n"
+ " parse JSON ATOMs as atoms of TYPE, and re-serialize\n"
+ " parse-atom-strings TYPE ATOM...\n"
+ " parse string ATOMs as atoms of given TYPE, and re-serialize\n"
+ " sort-atoms TYPE ATOM...\n"
+ " print JSON ATOMs in sorted order\n"
+ " parse-data TYPE DATUM...\n"
+ " parse JSON DATUMs as data of given TYPE, and re-serialize\n"
+ " parse-data-strings TYPE DATUM...\n"
+ " parse string DATUMs as data of given TYPE, and re-serialize\n"
+ " parse-column NAME OBJECT\n"
+ " parse column NAME with info OBJECT, and re-serialize\n"
+ " parse-table NAME OBJECT\n"
+ " parse table NAME with info OBJECT\n"
+ " parse-row TABLE ROW..., and re-serialize\n"
+ " parse each ROW of defined TABLE\n"
+ " compare-row TABLE ROW...\n"
+ " mutually compare all of the ROWs, print those that are equal\n"
+ " parse-conditions TABLE CONDITION...\n"
+ " parse each CONDITION on TABLE, and re-serialize\n"
+ " evaluate-conditions TABLE [CONDITION,...] [ROW,...]\n"
+ " test CONDITIONS on TABLE against each ROW, print results\n"
+ " parse-mutations TABLE MUTATION...\n"
+ " parse each MUTATION on TABLE, and re-serialize\n"
+ " execute-mutations TABLE [MUTATION,...] [ROW,...]\n"
+ " execute MUTATIONS on TABLE on each ROW, print results\n"
+ " query TABLE [ROW,...] [CONDITION,...]\n"
+ " add each ROW to TABLE, then query and print the rows that\n"
+ " satisfy each CONDITION.\n"
+ " query-distinct TABLE [ROW,...] [CONDITION,...] COLUMNS\n"
+ " add each ROW to TABLE, then query and print the rows that\n"
+ " satisfy each CONDITION and have distinct COLUMNS.\n"
+ " parse-schema JSON\n"
+ " parse JSON as an OVSDB schema, and re-serialize\n"
+ " transact COMMAND\n"
+ " execute each specified transactional COMMAND:\n"
+ " commit\n"
+ " abort\n"
+ " insert UUID I J\n"
+ " delete UUID\n"
+ " modify UUID I J\n"
+ " print\n"
+ " execute SCHEMA TRANSACTION...\n"
+ " executes each TRANSACTION on an initially empty database\n"
+ " the specified SCHEMA\n"
+ " trigger SCHEMA TRANSACTION...\n"
+ " executes each TRANSACTION on an initially empty database\n"
+ " the specified SCHEMA. A TRANSACTION of the form\n"
+ " [\"advance\", NUMBER] advances NUMBER milliseconds in\n"
+ " simulated time, for causing triggers to time out.\n"
+ " idl SERVER [TRANSACTION...]\n"
+ " connect to SERVER and dump the contents of the database\n"
+ " as seen initially by the IDL implementation and after\n"
+ " executing each TRANSACTION. (Each TRANSACTION must modify\n"
+ " the database or this command will hang.)\n",
+ program_name, program_name);
+ vlog_usage();
+ printf("\nOther options:\n"
+ " -t, --timeout=SECS give up after SECS seconds\n"
+ " -h, --help display this help message\n");
+ exit(EXIT_SUCCESS);
+}
+\f
+/* Command helper functions. */
+
+static struct json *
+parse_json(const char *s)
+{
+ struct json *json = json_from_string(s);
+ if (json->type == JSON_STRING) {
+ ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+ }
+ return json;
+}
+
+static struct json *
+unbox_json(struct json *json)
+{
+ if (json->type == JSON_ARRAY && json->u.array.n == 1) {
+ struct json *inner = json->u.array.elems[0];
+ json->u.array.elems[0] = NULL;
+ json_destroy(json);
+ return inner;
+ } else {
+ return json;
+ }
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+ char *string = json_to_string(json, JSSF_SORT);
+ json_destroy(json);
+ puts(string);
+ free(string);
+}
+
+static void
+print_and_free_ovsdb_error(struct ovsdb_error *error)
+{
+ char *string = ovsdb_error_to_string(error);
+ ovsdb_error_destroy(error);
+ puts(string);
+ free(string);
+}
+
+static void
+check_ovsdb_error(struct ovsdb_error *error)
+{
+ if (error) {
+ char *s = ovsdb_error_to_string(error);
+ ovsdb_error_destroy(error);
+ ovs_fatal(0, "%s", s);
+ }
+}
+
+static void
+die_if_error(char *error)
+{
+ if (error) {
+ ovs_fatal(0, "%s", error);
+ }
+}
+\f
+/* Command implementations. */
+
+static void
+do_log_io(int argc, char *argv[])
+{
+ const char *name = argv[1];
+ char *mode = argv[2];
+
+ struct ovsdb_error *error;
+ struct ovsdb_log *log;
+ char *save_ptr = NULL;
+ const char *token;
+ int flags;
+ int i;
+
+ for (flags = 0, token = strtok_r(mode, " |", &save_ptr); token != NULL;
+ token = strtok_r(NULL, " |", &save_ptr))
+ {
+ if (!strcmp(token, "O_RDONLY")) {
+ flags |= O_RDONLY;
+ } else if (!strcmp(token, "O_RDWR")) {
+ flags |= O_RDWR;
+ } else if (!strcmp(token, "O_TRUNC")) {
+ flags |= O_TRUNC;
+ } else if (!strcmp(token, "O_CREAT")) {
+ flags |= O_CREAT;
+ } else if (!strcmp(token, "O_EXCL")) {
+ flags |= O_EXCL;
+ } else if (!strcmp(token, "O_TRUNC")) {
+ flags |= O_TRUNC;
+ }
+ }
+
+ check_ovsdb_error(ovsdb_log_open(name, flags, &log));
+ printf("%s: open successful\n", name);
+
+ for (i = 3; i < argc; i++) {
+ const char *command = argv[i];
+ if (!strcmp(command, "read")) {
+ struct json *json;
+
+ error = ovsdb_log_read(log, &json);
+ if (!error) {
+ printf("%s: read: ", name);
+ if (json) {
+ print_and_free_json(json);
+ } else {
+ printf("end of log\n");
+ }
+ continue;
+ }
+ } else if (!strncmp(command, "write:", 6)) {
+ struct json *json = parse_json(command + 6);
+ error = ovsdb_log_write(log, json);
+ json_destroy(json);
+ } else if (!strcmp(command, "commit")) {
+ error = ovsdb_log_commit(log);
+ } else {
+ ovs_fatal(0, "unknown log-io command \"%s\"", command);
+ }
+ if (error) {
+ char *s = ovsdb_error_to_string(error);
+ printf("%s: %s failed: %s\n", name, command, s);
+ free(s);
+ ovsdb_error_destroy(error);
+ } else {
+ printf("%s: %s successful\n", name, command);
+ }
+ }
+
+ ovsdb_log_close(log);
+}
+
+static void
- do_parse_base_type(int argc UNUSED, char *argv[])
++do_parse_atomic_type(int argc OVS_UNUSED, char *argv[])
+{
+ enum ovsdb_atomic_type type;
+ struct json *json;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+ json_destroy(json);
+ print_and_free_json(ovsdb_atomic_type_to_json(type));
+}
+
+static void
- do_parse_type(int argc UNUSED, char *argv[])
++do_parse_base_type(int argc OVS_UNUSED, char *argv[])
+{
+ struct ovsdb_base_type base;
+ struct json *json;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_base_type_from_json(&base, json));
+ json_destroy(json);
+ print_and_free_json(ovsdb_base_type_to_json(&base));
+ ovsdb_base_type_destroy(&base);
+}
+
+static void
- do_sort_atoms(int argc UNUSED, char *argv[])
++do_parse_type(int argc OVS_UNUSED, char *argv[])
+{
+ struct ovsdb_type type;
+ struct json *json;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_type_from_json(&type, json));
+ json_destroy(json);
+ print_and_free_json(ovsdb_type_to_json(&type));
+ ovsdb_type_destroy(&type);
+}
+
+static void
+do_parse_atoms(int argc, char *argv[])
+{
+ struct ovsdb_base_type base;
+ struct json *json;
+ int i;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_base_type_from_json(&base, json));
+ json_destroy(json);
+
+ for (i = 2; i < argc; i++) {
+ struct ovsdb_error *error;
+ union ovsdb_atom atom;
+
+ json = unbox_json(parse_json(argv[i]));
+ error = ovsdb_atom_from_json(&atom, &base, json, NULL);
+ json_destroy(json);
+
+ if (error) {
+ print_and_free_ovsdb_error(error);
+ } else {
+ print_and_free_json(ovsdb_atom_to_json(&atom, base.type));
+ ovsdb_atom_destroy(&atom, base.type);
+ }
+ }
+ ovsdb_base_type_destroy(&base);
+}
+
+static void
+do_parse_atom_strings(int argc, char *argv[])
+{
+ struct ovsdb_base_type base;
+ struct json *json;
+ int i;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_base_type_from_json(&base, json));
+ json_destroy(json);
+
+ for (i = 2; i < argc; i++) {
+ union ovsdb_atom atom;
+ struct ds out;
+
+ die_if_error(ovsdb_atom_from_string(&atom, &base, argv[i]));
+
+ ds_init(&out);
+ ovsdb_atom_to_string(&atom, base.type, &out);
+ puts(ds_cstr(&out));
+ ds_destroy(&out);
+
+ ovsdb_atom_destroy(&atom, base.type);
+ }
+ ovsdb_base_type_destroy(&base);
+}
+
+static void
+do_parse_data(int argc, char *argv[])
+{
+ struct ovsdb_type type;
+ struct json *json;
+ int i;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_type_from_json(&type, json));
+ json_destroy(json);
+
+ for (i = 2; i < argc; i++) {
+ struct ovsdb_datum datum;
+
+ json = unbox_json(parse_json(argv[i]));
+ check_ovsdb_error(ovsdb_datum_from_json(&datum, &type, json, NULL));
+ json_destroy(json);
+
+ print_and_free_json(ovsdb_datum_to_json(&datum, &type));
+
+ ovsdb_datum_destroy(&datum, &type);
+ }
+ ovsdb_type_destroy(&type);
+}
+
+static void
+do_parse_data_strings(int argc, char *argv[])
+{
+ struct ovsdb_type type;
+ struct json *json;
+ int i;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_type_from_json(&type, json));
+ json_destroy(json);
+
+ for (i = 2; i < argc; i++) {
+ struct ovsdb_datum datum;
+ struct ds out;
+
+ die_if_error(ovsdb_datum_from_string(&datum, &type, argv[i]));
+
+ ds_init(&out);
+ ovsdb_datum_to_string(&datum, &type, &out);
+ puts(ds_cstr(&out));
+ ds_destroy(&out);
+
+ ovsdb_datum_destroy(&datum, &type);
+ }
+ ovsdb_type_destroy(&type);
+}
+
+static enum ovsdb_atomic_type compare_atoms_atomic_type;
+
+static int
+compare_atoms(const void *a_, const void *b_)
+{
+ const union ovsdb_atom *a = a_;
+ const union ovsdb_atom *b = b_;
+
+ return ovsdb_atom_compare_3way(a, b, compare_atoms_atomic_type);
+}
+
+static void
- do_parse_column(int argc UNUSED, char *argv[])
++do_sort_atoms(int argc OVS_UNUSED, char *argv[])
+{
+ struct ovsdb_base_type base;
+ union ovsdb_atom *atoms;
+ struct json *json, **json_atoms;
+ size_t n_atoms;
+ int i;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_base_type_from_json(&base, json));
+ json_destroy(json);
+
+ json = unbox_json(parse_json(argv[2]));
+ if (json->type != JSON_ARRAY) {
+ ovs_fatal(0, "second argument must be array");
+ }
+
+ /* Convert JSON atoms to internal representation. */
+ n_atoms = json->u.array.n;
+ atoms = xmalloc(n_atoms * sizeof *atoms);
+ for (i = 0; i < n_atoms; i++) {
+ check_ovsdb_error(ovsdb_atom_from_json(&atoms[i], &base,
+ json->u.array.elems[i], NULL));
+ }
+ json_destroy(json);
+
+ /* Sort atoms. */
+ compare_atoms_atomic_type = base.type;
+ qsort(atoms, n_atoms, sizeof *atoms, compare_atoms);
+
+ /* Convert internal representation back to JSON. */
+ json_atoms = xmalloc(n_atoms * sizeof *json_atoms);
+ for (i = 0; i < n_atoms; i++) {
+ json_atoms[i] = ovsdb_atom_to_json(&atoms[i], base.type);
+ ovsdb_atom_destroy(&atoms[i], base.type);
+ }
+ print_and_free_json(json_array_create(json_atoms, n_atoms));
+ free(atoms);
+ ovsdb_base_type_destroy(&base);
+}
+
+static void
- do_parse_table(int argc UNUSED, char *argv[])
++do_parse_column(int argc OVS_UNUSED, char *argv[])
+{
+ struct ovsdb_column *column;
+ struct json *json;
+
+ json = parse_json(argv[2]);
+ check_ovsdb_error(ovsdb_column_from_json(json, argv[1], &column));
+ json_destroy(json);
+ print_and_free_json(ovsdb_column_to_json(column));
+ ovsdb_column_destroy(column);
+}
+
+static void
- do_evaluate_conditions(int argc UNUSED, char *argv[])
++do_parse_table(int argc OVS_UNUSED, char *argv[])
+{
+ struct ovsdb_table_schema *ts;
+ struct json *json;
+
+ json = parse_json(argv[2]);
+ check_ovsdb_error(ovsdb_table_schema_from_json(json, argv[1], &ts));
+ json_destroy(json);
+ print_and_free_json(ovsdb_table_schema_to_json(ts));
+ ovsdb_table_schema_destroy(ts);
+}
+
+static void
+do_parse_rows(int argc, char *argv[])
+{
+ struct ovsdb_column_set all_columns;
+ struct ovsdb_table_schema *ts;
+ struct ovsdb_table *table;
+ struct json *json;
+ int i;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+ json_destroy(json);
+
+ table = ovsdb_table_create(ts);
+ ovsdb_column_set_init(&all_columns);
+ ovsdb_column_set_add_all(&all_columns, table);
+
+ for (i = 2; i < argc; i++) {
+ struct ovsdb_column_set columns;
+ struct ovsdb_row *row;
+
+ ovsdb_column_set_init(&columns);
+ row = ovsdb_row_create(table);
+
+ json = unbox_json(parse_json(argv[i]));
+ check_ovsdb_error(ovsdb_row_from_json(row, json, NULL, &columns));
+ json_destroy(json);
+
+ print_and_free_json(ovsdb_row_to_json(row, &all_columns));
+
+ if (columns.n_columns) {
+ struct svec names;
+ size_t j;
+ char *s;
+
+ svec_init(&names);
+ for (j = 0; j < columns.n_columns; j++) {
+ svec_add(&names, columns.columns[j]->name);
+ }
+ svec_sort(&names);
+ s = svec_join(&names, ", ", "");
+ puts(s);
+ free(s);
+ svec_destroy(&names);
+ } else {
+ printf("<none>\n");
+ }
+
+ ovsdb_column_set_destroy(&columns);
+ ovsdb_row_destroy(row);
+ }
+
+ ovsdb_column_set_destroy(&all_columns);
+ ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+static void
+do_compare_rows(int argc, char *argv[])
+{
+ struct ovsdb_column_set all_columns;
+ struct ovsdb_table_schema *ts;
+ struct ovsdb_table *table;
+ struct ovsdb_row **rows;
+ struct json *json;
+ char **names;
+ int n_rows;
+ int i, j;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+ json_destroy(json);
+
+ table = ovsdb_table_create(ts);
+ ovsdb_column_set_init(&all_columns);
+ ovsdb_column_set_add_all(&all_columns, table);
+
+ n_rows = argc - 2;
+ rows = xmalloc(sizeof *rows * n_rows);
+ names = xmalloc(sizeof *names * n_rows);
+ for (i = 0; i < n_rows; i++) {
+ rows[i] = ovsdb_row_create(table);
+
+ json = parse_json(argv[i + 2]);
+ if (json->type != JSON_ARRAY || json->u.array.n != 2
+ || json->u.array.elems[0]->type != JSON_STRING) {
+ ovs_fatal(0, "\"%s\" does not have expected form "
+ "[\"name\", {data}]", argv[i]);
+ }
+ names[i] = xstrdup(json->u.array.elems[0]->u.string);
+ check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[1],
+ NULL, NULL));
+ json_destroy(json);
+ }
+ for (i = 0; i < n_rows; i++) {
+ uint32_t i_hash = ovsdb_row_hash_columns(rows[i], &all_columns, 0);
+ for (j = i + 1; j < n_rows; j++) {
+ uint32_t j_hash = ovsdb_row_hash_columns(rows[j], &all_columns, 0);
+ if (ovsdb_row_equal_columns(rows[i], rows[j], &all_columns)) {
+ printf("%s == %s\n", names[i], names[j]);
+ if (i_hash != j_hash) {
+ printf("but hash(%s) != hash(%s)\n", names[i], names[j]);
+ abort();
+ }
+ } else if (i_hash == j_hash) {
+ printf("hash(%s) == hash(%s)\n", names[i], names[j]);
+ }
+ }
+ }
+ for (i = 0; i < n_rows; i++) {
+ ovsdb_row_destroy(rows[i]);
+ free(names[i]);
+ }
+ free(rows);
+ free(names);
+
+ ovsdb_column_set_destroy(&all_columns);
+ ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+static void
+do_parse_conditions(int argc, char *argv[])
+{
+ struct ovsdb_table_schema *ts;
+ struct json *json;
+ int exit_code = 0;
+ int i;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+ json_destroy(json);
+
+ for (i = 2; i < argc; i++) {
+ struct ovsdb_condition cnd;
+ struct ovsdb_error *error;
+
+ json = parse_json(argv[i]);
+ error = ovsdb_condition_from_json(ts, json, NULL, &cnd);
+ if (!error) {
+ print_and_free_json(ovsdb_condition_to_json(&cnd));
+ } else {
+ char *s = ovsdb_error_to_string(error);
+ ovs_error(0, "%s", s);
+ free(s);
+ ovsdb_error_destroy(error);
+ exit_code = 1;
+ }
+ json_destroy(json);
+
+ ovsdb_condition_destroy(&cnd);
+ }
+ ovsdb_table_schema_destroy(ts);
+
+ exit(exit_code);
+}
+
+static void
- do_execute_mutations(int argc UNUSED, char *argv[])
++do_evaluate_conditions(int argc OVS_UNUSED, char *argv[])
+{
+ struct ovsdb_table_schema *ts;
+ struct ovsdb_table *table;
+ struct ovsdb_condition *conditions;
+ size_t n_conditions;
+ struct ovsdb_row **rows;
+ size_t n_rows;
+ struct json *json;
+ size_t i, j;
+
+ /* Parse table schema, create table. */
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+ json_destroy(json);
+
+ table = ovsdb_table_create(ts);
+
+ /* Parse conditions. */
+ json = parse_json(argv[2]);
+ if (json->type != JSON_ARRAY) {
+ ovs_fatal(0, "CONDITION argument is not JSON array");
+ }
+ n_conditions = json->u.array.n;
+ conditions = xmalloc(n_conditions * sizeof *conditions);
+ for (i = 0; i < n_conditions; i++) {
+ check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+ NULL, &conditions[i]));
+ }
+ json_destroy(json);
+
+ /* Parse rows. */
+ json = parse_json(argv[3]);
+ if (json->type != JSON_ARRAY) {
+ ovs_fatal(0, "ROW argument is not JSON array");
+ }
+ n_rows = json->u.array.n;
+ rows = xmalloc(n_rows * sizeof *rows);
+ for (i = 0; i < n_rows; i++) {
+ rows[i] = ovsdb_row_create(table);
+ check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[i],
+ NULL, NULL));
+ }
+ json_destroy(json);
+
+ for (i = 0; i < n_conditions; i++) {
+ printf("condition %2d:", i);
+ for (j = 0; j < n_rows; j++) {
+ bool result = ovsdb_condition_evaluate(rows[j], &conditions[i]);
+ if (j % 5 == 0) {
+ putchar(' ');
+ }
+ putchar(result ? 'T' : '-');
+ }
+ printf("\n");
+ }
+
+ for (i = 0; i < n_conditions; i++) {
+ ovsdb_condition_destroy(&conditions[i]);
+ }
+ free(conditions);
+ for (i = 0; i < n_rows; i++) {
+ ovsdb_row_destroy(rows[i]);
+ }
+ free(rows);
+ ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+static void
+do_parse_mutations(int argc, char *argv[])
+{
+ struct ovsdb_table_schema *ts;
+ struct json *json;
+ int exit_code = 0;
+ int i;
+
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+ json_destroy(json);
+
+ for (i = 2; i < argc; i++) {
+ struct ovsdb_mutation_set set;
+ struct ovsdb_error *error;
+
+ json = parse_json(argv[i]);
+ error = ovsdb_mutation_set_from_json(ts, json, NULL, &set);
+ if (!error) {
+ print_and_free_json(ovsdb_mutation_set_to_json(&set));
+ } else {
+ char *s = ovsdb_error_to_string(error);
+ ovs_error(0, "%s", s);
+ free(s);
+ ovsdb_error_destroy(error);
+ exit_code = 1;
+ }
+ json_destroy(json);
+
+ ovsdb_mutation_set_destroy(&set);
+ }
+ ovsdb_table_schema_destroy(ts);
+
+ exit(exit_code);
+}
+
+static void
- do_query(int argc UNUSED, char *argv[])
++do_execute_mutations(int argc OVS_UNUSED, char *argv[])
+{
+ struct ovsdb_table_schema *ts;
+ struct ovsdb_table *table;
+ struct ovsdb_mutation_set *sets;
+ size_t n_sets;
+ struct ovsdb_row **rows;
+ size_t n_rows;
+ struct json *json;
+ size_t i, j;
+
+ /* Parse table schema, create table. */
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+ json_destroy(json);
+
+ table = ovsdb_table_create(ts);
+
+ /* Parse mutations. */
+ json = parse_json(argv[2]);
+ if (json->type != JSON_ARRAY) {
+ ovs_fatal(0, "MUTATION argument is not JSON array");
+ }
+ n_sets = json->u.array.n;
+ sets = xmalloc(n_sets * sizeof *sets);
+ for (i = 0; i < n_sets; i++) {
+ check_ovsdb_error(ovsdb_mutation_set_from_json(ts,
+ json->u.array.elems[i],
+ NULL, &sets[i]));
+ }
+ json_destroy(json);
+
+ /* Parse rows. */
+ json = parse_json(argv[3]);
+ if (json->type != JSON_ARRAY) {
+ ovs_fatal(0, "ROW argument is not JSON array");
+ }
+ n_rows = json->u.array.n;
+ rows = xmalloc(n_rows * sizeof *rows);
+ for (i = 0; i < n_rows; i++) {
+ rows[i] = ovsdb_row_create(table);
+ check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[i],
+ NULL, NULL));
+ }
+ json_destroy(json);
+
+ for (i = 0; i < n_sets; i++) {
+ printf("mutation %2d:\n", i);
+ for (j = 0; j < n_rows; j++) {
+ struct ovsdb_error *error;
+ struct ovsdb_row *row;
+
+ row = ovsdb_row_clone(rows[j]);
+ error = ovsdb_mutation_set_execute(row, &sets[i]);
+
+ printf("row %zu: ", j);
+ if (error) {
+ print_and_free_ovsdb_error(error);
+ } else {
+ struct ovsdb_column_set columns;
+ struct shash_node *node;
+
+ ovsdb_column_set_init(&columns);
+ SHASH_FOR_EACH (node, &ts->columns) {
+ struct ovsdb_column *c = node->data;
+ if (!ovsdb_datum_equals(&row->fields[c->index],
+ &rows[j]->fields[c->index],
+ &c->type)) {
+ ovsdb_column_set_add(&columns, c);
+ }
+ }
+ if (columns.n_columns) {
+ print_and_free_json(ovsdb_row_to_json(row, &columns));
+ } else {
+ printf("no change\n");
+ }
+ ovsdb_column_set_destroy(&columns);
+ }
+ ovsdb_row_destroy(row);
+ }
+ printf("\n");
+ }
+
+ for (i = 0; i < n_sets; i++) {
+ ovsdb_mutation_set_destroy(&sets[i]);
+ }
+ free(sets);
+ for (i = 0; i < n_rows; i++) {
+ ovsdb_row_destroy(rows[i]);
+ }
+ free(rows);
+ ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+struct do_query_cbdata {
+ struct uuid *row_uuids;
+ int *counts;
+ size_t n_rows;
+};
+
+static bool
+do_query_cb(const struct ovsdb_row *row, void *cbdata_)
+{
+ struct do_query_cbdata *cbdata = cbdata_;
+ size_t i;
+
+ for (i = 0; i < cbdata->n_rows; i++) {
+ if (uuid_equals(ovsdb_row_get_uuid(row), &cbdata->row_uuids[i])) {
+ cbdata->counts[i]++;
+ }
+ }
+
+ return true;
+}
+
+static void
- do_query_distinct(int argc UNUSED, char *argv[])
++do_query(int argc OVS_UNUSED, char *argv[])
+{
+ struct do_query_cbdata cbdata;
+ struct ovsdb_table_schema *ts;
+ struct ovsdb_table *table;
+ struct json *json;
+ int exit_code = 0;
+ size_t i;
+
+ /* Parse table schema, create table. */
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+ json_destroy(json);
+
+ table = ovsdb_table_create(ts);
+
+ /* Parse rows, add to table. */
+ json = parse_json(argv[2]);
+ if (json->type != JSON_ARRAY) {
+ ovs_fatal(0, "ROW argument is not JSON array");
+ }
+ cbdata.n_rows = json->u.array.n;
+ cbdata.row_uuids = xmalloc(cbdata.n_rows * sizeof *cbdata.row_uuids);
+ cbdata.counts = xmalloc(cbdata.n_rows * sizeof *cbdata.counts);
+ for (i = 0; i < cbdata.n_rows; i++) {
+ struct ovsdb_row *row = ovsdb_row_create(table);
+ uuid_generate(ovsdb_row_get_uuid_rw(row));
+ check_ovsdb_error(ovsdb_row_from_json(row, json->u.array.elems[i],
+ NULL, NULL));
+ if (ovsdb_table_get_row(table, ovsdb_row_get_uuid(row))) {
+ ovs_fatal(0, "duplicate UUID "UUID_FMT" in table",
+ UUID_ARGS(ovsdb_row_get_uuid(row)));
+ }
+ cbdata.row_uuids[i] = *ovsdb_row_get_uuid(row);
+ ovsdb_table_put_row(table, row);
+ }
+ json_destroy(json);
+
+ /* Parse conditions and execute queries. */
+ json = parse_json(argv[3]);
+ if (json->type != JSON_ARRAY) {
+ ovs_fatal(0, "CONDITION argument is not JSON array");
+ }
+ for (i = 0; i < json->u.array.n; i++) {
+ struct ovsdb_condition cnd;
+ size_t j;
+
+ check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+ NULL, &cnd));
+
+ memset(cbdata.counts, 0, cbdata.n_rows * sizeof *cbdata.counts);
+ ovsdb_query(table, &cnd, do_query_cb, &cbdata);
+
+ printf("query %2d:", i);
+ for (j = 0; j < cbdata.n_rows; j++) {
+ if (j % 5 == 0) {
+ putchar(' ');
+ }
+ if (cbdata.counts[j]) {
+ printf("%d", cbdata.counts[j]);
+ if (cbdata.counts[j] > 1) {
+ /* Dup! */
+ exit_code = 1;
+ }
+ } else {
+ putchar('-');
+ }
+ }
+ putchar('\n');
+
+ ovsdb_condition_destroy(&cnd);
+ }
+ json_destroy(json);
+
+ ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+
+ exit(exit_code);
+}
+
+struct do_query_distinct_class {
+ struct ovsdb_row *example;
+ int count;
+};
+
+struct do_query_distinct_row {
+ struct uuid uuid;
+ struct do_query_distinct_class *class;
+};
+
+static void
- do_parse_schema(int argc UNUSED, char *argv[])
++do_query_distinct(int argc OVS_UNUSED, char *argv[])
+{
+ struct ovsdb_column_set columns;
+ struct ovsdb_table_schema *ts;
+ struct ovsdb_table *table;
+ struct do_query_distinct_row *rows;
+ size_t n_rows;
+ struct do_query_distinct_class *classes;
+ size_t n_classes;
+ struct json *json;
+ int exit_code = 0;
+ size_t i, j, k;
+
+ /* Parse table schema, create table. */
+ json = unbox_json(parse_json(argv[1]));
+ check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+ json_destroy(json);
+
+ table = ovsdb_table_create(ts);
+
+ /* Parse column set. */
+ json = parse_json(argv[4]);
+ check_ovsdb_error(ovsdb_column_set_from_json(json, table, &columns));
+ json_destroy(json);
+
+ /* Parse rows, add to table. */
+ json = parse_json(argv[2]);
+ if (json->type != JSON_ARRAY) {
+ ovs_fatal(0, "ROW argument is not JSON array");
+ }
+ n_rows = json->u.array.n;
+ rows = xmalloc(n_rows * sizeof *rows);
+ classes = xmalloc(n_rows * sizeof *classes);
+ n_classes = 0;
+ for (i = 0; i < n_rows; i++) {
+ struct ovsdb_row *row;
+ size_t j;
+
+ /* Parse row. */
+ row = ovsdb_row_create(table);
+ uuid_generate(ovsdb_row_get_uuid_rw(row));
+ check_ovsdb_error(ovsdb_row_from_json(row, json->u.array.elems[i],
+ NULL, NULL));
+
+ /* Initialize row and find equivalence class. */
+ rows[i].uuid = *ovsdb_row_get_uuid(row);
+ rows[i].class = NULL;
+ for (j = 0; j < n_classes; j++) {
+ if (ovsdb_row_equal_columns(row, classes[j].example, &columns)) {
+ rows[i].class = &classes[j];
+ break;
+ }
+ }
+ if (!rows[i].class) {
+ rows[i].class = &classes[n_classes];
+ classes[n_classes].example = ovsdb_row_clone(row);
+ n_classes++;
+ }
+
+ /* Add row to table. */
+ if (ovsdb_table_get_row(table, ovsdb_row_get_uuid(row))) {
+ ovs_fatal(0, "duplicate UUID "UUID_FMT" in table",
+ UUID_ARGS(ovsdb_row_get_uuid(row)));
+ }
+ ovsdb_table_put_row(table, row);
+
+ }
+ json_destroy(json);
+
+ /* Parse conditions and execute queries. */
+ json = parse_json(argv[3]);
+ if (json->type != JSON_ARRAY) {
+ ovs_fatal(0, "CONDITION argument is not JSON array");
+ }
+ for (i = 0; i < json->u.array.n; i++) {
+ struct ovsdb_row_set results;
+ struct ovsdb_condition cnd;
+
+ check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+ NULL, &cnd));
+
+ for (j = 0; j < n_classes; j++) {
+ classes[j].count = 0;
+ }
+ ovsdb_row_set_init(&results);
+ ovsdb_query_distinct(table, &cnd, &columns, &results);
+ for (j = 0; j < results.n_rows; j++) {
+ for (k = 0; k < n_rows; k++) {
+ if (uuid_equals(ovsdb_row_get_uuid(results.rows[j]),
+ &rows[k].uuid)) {
+ rows[k].class->count++;
+ }
+ }
+ }
+ ovsdb_row_set_destroy(&results);
+
+ printf("query %2d:", i);
+ for (j = 0; j < n_rows; j++) {
+ int count = rows[j].class->count;
+
+ if (j % 5 == 0) {
+ putchar(' ');
+ }
+ if (count > 1) {
+ /* Dup! */
+ printf("%d", count);
+ exit_code = 1;
+ } else if (count == 1) {
+ putchar("abcdefghijklmnopqrstuvwxyz"[rows[j].class - classes]);
+ } else {
+ putchar('-');
+ }
+ }
+ putchar('\n');
+
+ ovsdb_condition_destroy(&cnd);
+ }
+ json_destroy(json);
+
+ ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+
+ exit(exit_code);
+}
+
+static void
- do_execute(int argc UNUSED, char *argv[])
++do_parse_schema(int argc OVS_UNUSED, char *argv[])
+{
+ struct ovsdb_schema *schema;
+ struct json *json;
+
+ json = parse_json(argv[1]);
+ check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+ json_destroy(json);
+ print_and_free_json(ovsdb_schema_to_json(schema));
+ ovsdb_schema_destroy(schema);
+}
+
+static void
- do_trigger(int argc UNUSED, char *argv[])
++do_execute(int argc OVS_UNUSED, char *argv[])
+{
+ struct ovsdb_schema *schema;
+ struct json *json;
+ struct ovsdb *db;
+ int i;
+
+ /* Create database. */
+ json = parse_json(argv[1]);
+ check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+ json_destroy(json);
+ db = ovsdb_create(schema);
+
+ for (i = 2; i < argc; i++) {
+ struct json *params, *result;
+ char *s;
+
+ params = parse_json(argv[i]);
+ result = ovsdb_execute(db, params, 0, NULL);
+ s = json_to_string(result, JSSF_SORT);
+ printf("%s\n", s);
+ free(s);
+ json_destroy(params);
+ json_destroy(result);
+ }
+
+ ovsdb_destroy(db);
+}
+
+struct test_trigger {
+ struct ovsdb_trigger trigger;
+ int number;
+};
+
+static void
+do_trigger_dump(struct test_trigger *t, long long int now, const char *title)
+{
+ struct json *result;
+ char *s;
+
+ result = ovsdb_trigger_steal_result(&t->trigger);
+ s = json_to_string(result, JSSF_SORT);
+ printf("t=%lld: trigger %d (%s): %s\n", now, t->number, title, s);
+ free(s);
+ json_destroy(result);
+ ovsdb_trigger_destroy(&t->trigger);
+ free(t);
+}
+
+static void
- do_help(int argc UNUSED, char *argv[] UNUSED)
++do_trigger(int argc OVS_UNUSED, char *argv[])
+{
+ struct ovsdb_schema *schema;
+ struct list completions;
+ struct json *json;
+ struct ovsdb *db;
+ long long int now;
+ int number;
+ int i;
+
+ /* Create database. */
+ json = parse_json(argv[1]);
+ check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+ json_destroy(json);
+ db = ovsdb_create(schema);
+
+ list_init(&completions);
+ now = 0;
+ number = 0;
+ for (i = 2; i < argc; i++) {
+ struct json *params = parse_json(argv[i]);
+ if (params->type == JSON_ARRAY
+ && json_array(params)->n == 2
+ && json_array(params)->elems[0]->type == JSON_STRING
+ && !strcmp(json_string(json_array(params)->elems[0]), "advance")
+ && json_array(params)->elems[1]->type == JSON_INTEGER) {
+ now += json_integer(json_array(params)->elems[1]);
+ json_destroy(params);
+ } else {
+ struct test_trigger *t = xmalloc(sizeof *t);
+ ovsdb_trigger_init(db, &t->trigger, params, &completions, now);
+ t->number = number++;
+ if (ovsdb_trigger_is_complete(&t->trigger)) {
+ do_trigger_dump(t, now, "immediate");
+ } else {
+ printf("t=%lld: new trigger %d\n", now, t->number);
+ }
+ }
+
+ ovsdb_trigger_run(db, now);
+ while (!list_is_empty(&completions)) {
+ do_trigger_dump(CONTAINER_OF(list_pop_front(&completions),
+ struct test_trigger, trigger.node),
+ now, "delayed");
+ }
+
+ ovsdb_trigger_wait(db, now);
+ poll_immediate_wake();
+ poll_block();
+ }
+
+ ovsdb_destroy(db);
+}
+
+static void
- do_transact_commit(int argc UNUSED, char *argv[] UNUSED)
++do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ usage();
+}
+\f
+/* "transact" command. */
+
+static struct ovsdb *do_transact_db;
+static struct ovsdb_txn *do_transact_txn;
+static struct ovsdb_table *do_transact_table;
+
+static void
- do_transact_abort(int argc UNUSED, char *argv[] UNUSED)
++do_transact_commit(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ ovsdb_txn_commit(do_transact_txn, false);
+ do_transact_txn = NULL;
+}
+
+static void
- do_transact_insert(int argc UNUSED, char *argv[] UNUSED)
++do_transact_abort(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ ovsdb_txn_abort(do_transact_txn);
+ do_transact_txn = NULL;
+}
+
+static void
+uuid_from_integer(int integer, struct uuid *uuid)
+{
+ uuid_zero(uuid);
+ uuid->parts[3] = integer;
+}
+
+static const struct ovsdb_row *
+do_transact_find_row(const char *uuid_string)
+{
+ const struct ovsdb_row *row;
+ struct uuid uuid;
+
+ uuid_from_integer(atoi(uuid_string), &uuid);
+ row = ovsdb_table_get_row(do_transact_table, &uuid);
+ if (!row) {
+ ovs_fatal(0, "table does not contain row with UUID "UUID_FMT,
+ UUID_ARGS(&uuid));
+ }
+ return row;
+}
+
+static void
+do_transact_set_integer(struct ovsdb_row *row, const char *column_name,
+ int integer)
+{
+ if (integer != -1) {
+ const struct ovsdb_column *column;
+
+ column = ovsdb_table_schema_get_column(do_transact_table->schema,
+ column_name);
+ row->fields[column->index].keys[0].integer = integer;
+ }
+}
+
+static int
+do_transact_get_integer(const struct ovsdb_row *row, const char *column_name)
+{
+ const struct ovsdb_column *column;
+
+ column = ovsdb_table_schema_get_column(do_transact_table->schema,
+ column_name);
+ return row->fields[column->index].keys[0].integer;
+}
+
+static void
+do_transact_set_i_j(struct ovsdb_row *row,
+ const char *i_string, const char *j_string)
+{
+ do_transact_set_integer(row, "i", atoi(i_string));
+ do_transact_set_integer(row, "j", atoi(j_string));
+}
+
+static void
- do_transact_delete(int argc UNUSED, char *argv[] UNUSED)
++do_transact_insert(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ struct ovsdb_row *row;
+ struct uuid *uuid;
+
+ row = ovsdb_row_create(do_transact_table);
+
+ /* Set UUID. */
+ uuid = ovsdb_row_get_uuid_rw(row);
+ uuid_from_integer(atoi(argv[1]), uuid);
+ if (ovsdb_table_get_row(do_transact_table, uuid)) {
+ ovs_fatal(0, "table already contains row with UUID "UUID_FMT,
+ UUID_ARGS(uuid));
+ }
+
+ do_transact_set_i_j(row, argv[2], argv[3]);
+
+ /* Insert row. */
+ ovsdb_txn_row_insert(do_transact_txn, row);
+}
+
+static void
- do_transact_modify(int argc UNUSED, char *argv[] UNUSED)
++do_transact_delete(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ const struct ovsdb_row *row = do_transact_find_row(argv[1]);
+ ovsdb_txn_row_delete(do_transact_txn, row);
+}
+
+static void
- do_transact_print(int argc UNUSED, char *argv[] UNUSED)
++do_transact_modify(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ const struct ovsdb_row *row_ro;
+ struct ovsdb_row *row_rw;
+
+ row_ro = do_transact_find_row(argv[1]);
+ row_rw = ovsdb_txn_row_modify(do_transact_txn, row_ro);
+ do_transact_set_i_j(row_rw, argv[2], argv[3]);
+}
+
+static int
+compare_rows_by_uuid(const void *a_, const void *b_)
+{
+ struct ovsdb_row *const *ap = a_;
+ struct ovsdb_row *const *bp = b_;
+
+ return uuid_compare_3way(ovsdb_row_get_uuid(*ap), ovsdb_row_get_uuid(*bp));
+}
+
+static void
++do_transact_print(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ const struct ovsdb_row **rows;
+ const struct ovsdb_row *row;
+ size_t n_rows;
+ size_t i;
+
+ n_rows = hmap_count(&do_transact_table->rows);
+ rows = xmalloc(n_rows * sizeof *rows);
+ i = 0;
+ HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node,
+ &do_transact_table->rows) {
+ rows[i++] = row;
+ }
+ assert(i == n_rows);
+
+ qsort(rows, n_rows, sizeof *rows, compare_rows_by_uuid);
+
+ for (i = 0; i < n_rows; i++) {
+ printf("\n%"PRId32": i=%d, j=%d",
+ ovsdb_row_get_uuid(rows[i])->parts[3],
+ do_transact_get_integer(rows[i], "i"),
+ do_transact_get_integer(rows[i], "j"));
+ }
+
+ free(rows);
+}
+
+static void
+do_transact(int argc, char *argv[])
+{
+ static const struct command do_transact_commands[] = {
+ { "commit", 0, 0, do_transact_commit },
+ { "abort", 0, 0, do_transact_abort },
+ { "insert", 2, 3, do_transact_insert },
+ { "delete", 1, 1, do_transact_delete },
+ { "modify", 2, 3, do_transact_modify },
+ { "print", 0, 0, do_transact_print },
+ { NULL, 0, 0, NULL },
+ };
+
+ struct ovsdb_schema *schema;
+ struct json *json;
+ int i;
+
+ /* Create table. */
+ json = parse_json("{\"name\": \"testdb\", "
+ " \"tables\": "
+ " {\"mytable\": "
+ " {\"columns\": "
+ " {\"i\": {\"type\": \"integer\"}, "
+ " \"j\": {\"type\": \"integer\"}}}}}");
+ check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+ json_destroy(json);
+ do_transact_db = ovsdb_create(schema);
+ do_transact_table = ovsdb_get_table(do_transact_db, "mytable");
+ assert(do_transact_table != NULL);
+
+ for (i = 1; i < argc; i++) {
+ struct json *command;
+ size_t n_args;
+ char **args;
+ int j;
+
+ command = parse_json(argv[i]);
+ if (command->type != JSON_ARRAY) {
+ ovs_fatal(0, "transaction %d must be JSON array "
+ "with at least 1 element", i);
+ }
+
+ n_args = command->u.array.n;
+ args = xmalloc((n_args + 1) * sizeof *args);
+ for (j = 0; j < n_args; j++) {
+ struct json *s = command->u.array.elems[j];
+ if (s->type != JSON_STRING) {
+ ovs_fatal(0, "transaction %d argument %d must be JSON string",
+ i, j);
+ }
+ args[j] = xstrdup(json_string(s));
+ }
+ args[n_args] = NULL;
+
+ if (!do_transact_txn) {
+ do_transact_txn = ovsdb_txn_create(do_transact_db);
+ }
+
+ for (j = 0; j < n_args; j++) {
+ if (j) {
+ putchar(' ');
+ }
+ fputs(args[j], stdout);
+ }
+ fputs(":", stdout);
+ run_command(n_args, args, do_transact_commands);
+ putchar('\n');
+
+ for (j = 0; j < n_args; j++) {
+ free(args[j]);
+ }
+ free(args);
+ json_destroy(command);
+ }
+ ovsdb_txn_abort(do_transact_txn);
+ ovsdb_destroy(do_transact_db); /* Also destroys 'schema'. */
+}
+
+static int
+compare_link1(const void *a_, const void *b_)
+{
+ const struct idltest_link1 *const *ap = a_;
+ const struct idltest_link1 *const *bp = b_;
+ const struct idltest_link1 *a = *ap;
+ const struct idltest_link1 *b = *bp;
+
+ return a->i < b->i ? -1 : a->i > b->i;
+}
+
+static void
+print_idl(struct ovsdb_idl *idl, int step)
+{
+ const struct idltest_simple *s;
+ const struct idltest_link1 *l1;
+ const struct idltest_link2 *l2;
+ int n = 0;
+
+ IDLTEST_SIMPLE_FOR_EACH (s, idl) {
+ size_t i;
+
+ printf("%03d: i=%"PRId64" r=%g b=%s s=%s u="UUID_FMT" ia=[",
+ step, s->i, s->r, s->b ? "true" : "false",
+ s->s, UUID_ARGS(&s->u));
+ for (i = 0; i < s->n_ia; i++) {
+ printf("%s%"PRId64, i ? " " : "", s->ia[i]);
+ }
+ printf("] ra=[");
+ for (i = 0; i < s->n_ra; i++) {
+ printf("%s%g", i ? " " : "", s->ra[i]);
+ }
+ printf("] ba=[");
+ for (i = 0; i < s->n_ba; i++) {
+ printf("%s%s", i ? " " : "", s->ba[i] ? "true" : "false");
+ }
+ printf("] sa=[");
+ for (i = 0; i < s->n_sa; i++) {
+ printf("%s%s", i ? " " : "", s->sa[i]);
+ }
+ printf("] ua=[");
+ for (i = 0; i < s->n_ua; i++) {
+ printf("%s"UUID_FMT, i ? " " : "", UUID_ARGS(&s->ua[i]));
+ }
+ printf("] uuid="UUID_FMT"\n", UUID_ARGS(&s->header_.uuid));
+ n++;
+ }
+ IDLTEST_LINK1_FOR_EACH (l1, idl) {
+ struct idltest_link1 **links;
+ size_t i;
+
+ printf("%03d: i=%"PRId64" k=", step, l1->i);
+ if (l1->k) {
+ printf("%"PRId64, l1->k->i);
+ }
+ printf(" ka=[");
+ links = xmemdup(l1->ka, l1->n_ka * sizeof *l1->ka);
+ qsort(links, l1->n_ka, sizeof *links, compare_link1);
+ for (i = 0; i < l1->n_ka; i++) {
+ printf("%s%"PRId64, i ? " " : "", links[i]->i);
+ }
+ free(links);
+ printf("] l2=");
+ if (l1->l2) {
+ printf("%"PRId64, l1->l2->i);
+ }
+ printf(" uuid="UUID_FMT"\n", UUID_ARGS(&l1->header_.uuid));
+ n++;
+ }
+ IDLTEST_LINK2_FOR_EACH (l2, idl) {
+ printf("%03d: i=%"PRId64" l1=", step, l2->i);
+ if (l2->l1) {
+ printf("%"PRId64, l2->l1->i);
+ }
+ printf(" uuid="UUID_FMT"\n", UUID_ARGS(&l2->header_.uuid));
+ n++;
+ }
+ if (!n) {
+ printf("%03d: empty\n", step);
+ }
+}
+
+static unsigned int
+print_updated_idl(struct ovsdb_idl *idl, struct jsonrpc *rpc,
+ int step, unsigned int seqno)
+{
+ for (;;) {
+ unsigned int new_seqno;
+
+ if (rpc) {
+ jsonrpc_run(rpc);
+ }
+ ovsdb_idl_run(idl);
+ new_seqno = ovsdb_idl_get_seqno(idl);
+ if (new_seqno != seqno) {
+ print_idl(idl, step);
+ return new_seqno;
+ }
+
+ if (rpc) {
+ jsonrpc_wait(rpc);
+ }
+ ovsdb_idl_wait(idl);
+ poll_block();
+ }
+}
+
+static void
+parse_uuids(const struct json *json, struct ovsdb_symbol_table *symtab,
+ size_t *n)
+{
+ struct uuid uuid;
+
+ if (json->type == JSON_STRING && uuid_from_string(&uuid, json->u.string)) {
+ char *name = xasprintf("#%d#", *n);
+ fprintf(stderr, "%s = "UUID_FMT"\n", name, UUID_ARGS(&uuid));
+ ovsdb_symbol_table_put(symtab, name, &uuid, false);
+ free(name);
+ *n += 1;
+ } else if (json->type == JSON_ARRAY) {
+ size_t i;
+
+ for (i = 0; i < json->u.array.n; i++) {
+ parse_uuids(json->u.array.elems[i], symtab, n);
+ }
+ } else if (json->type == JSON_OBJECT) {
+ const struct shash_node *node;
+
+ SHASH_FOR_EACH (node, json_object(json)) {
+ parse_uuids(node->data, symtab, n);
+ }
+ }
+}
+
+static void
+substitute_uuids(struct json *json, const struct ovsdb_symbol_table *symtab)
+{
+ if (json->type == JSON_STRING) {
+ const struct ovsdb_symbol *symbol;
+
+ symbol = ovsdb_symbol_table_get(symtab, json->u.string);
+ if (symbol) {
+ free(json->u.string);
+ json->u.string = xasprintf(UUID_FMT, UUID_ARGS(&symbol->uuid));
+ }
+ } else if (json->type == JSON_ARRAY) {
+ size_t i;
+
+ for (i = 0; i < json->u.array.n; i++) {
+ substitute_uuids(json->u.array.elems[i], symtab);
+ }
+ } else if (json->type == JSON_OBJECT) {
+ const struct shash_node *node;
+
+ SHASH_FOR_EACH (node, json_object(json)) {
+ substitute_uuids(node->data, symtab);
+ }
+ }
+}
+
+static const struct idltest_simple *
+idltest_find_simple(struct ovsdb_idl *idl, int i)
+{
+ const struct idltest_simple *s;
+
+ IDLTEST_SIMPLE_FOR_EACH (s, idl) {
+ if (s->i == i) {
+ return s;
+ }
+ }
+ return NULL;
+}
+
+static void
+idl_set(struct ovsdb_idl *idl, char *commands, int step)
+{
+ char *cmd, *save_ptr1 = NULL;
+ struct ovsdb_idl_txn *txn;
+ enum ovsdb_idl_txn_status status;
+ bool increment = false;
+
+ txn = ovsdb_idl_txn_create(idl);
+ for (cmd = strtok_r(commands, ",", &save_ptr1); cmd;
+ cmd = strtok_r(NULL, ",", &save_ptr1)) {
+ char *save_ptr2 = NULL;
+ char *name, *arg1, *arg2, *arg3;
+
+ name = strtok_r(cmd, " ", &save_ptr2);
+ arg1 = strtok_r(NULL, " ", &save_ptr2);
+ arg2 = strtok_r(NULL, " ", &save_ptr2);
+ arg3 = strtok_r(NULL, " ", &save_ptr2);
+
+ if (!strcmp(name, "set")) {
+ const struct idltest_simple *s;
+
+ if (!arg3) {
+ ovs_fatal(0, "\"set\" command requires 3 arguments");
+ }
+
+ s = idltest_find_simple(idl, atoi(arg1));
+ if (!s) {
+ ovs_fatal(0, "\"set\" command asks for nonexistent "
+ "i=%d", atoi(arg1));
+ }
+
+ if (!strcmp(arg2, "b")) {
+ idltest_simple_set_b(s, atoi(arg3));
+ } else if (!strcmp(arg2, "s")) {
+ idltest_simple_set_s(s, arg3);
+ } else if (!strcmp(arg2, "u")) {
+ struct uuid uuid;
+ uuid_from_string(&uuid, arg3);
+ idltest_simple_set_u(s, uuid);
+ } else if (!strcmp(arg2, "r")) {
+ idltest_simple_set_r(s, atof(arg3));
+ } else {
+ ovs_fatal(0, "\"set\" command asks for unknown column %s",
+ arg2);
+ }
+ } else if (!strcmp(name, "insert")) {
+ struct idltest_simple *s;
+
+ if (!arg1 || arg2) {
+ ovs_fatal(0, "\"set\" command requires 1 argument");
+ }
+
+ s = idltest_simple_insert(txn);
+ idltest_simple_set_i(s, atoi(arg1));
+ } else if (!strcmp(name, "delete")) {
+ const struct idltest_simple *s;
+
+ if (!arg1 || arg2) {
+ ovs_fatal(0, "\"set\" command requires 1 argument");
+ }
+
+ s = idltest_find_simple(idl, atoi(arg1));
+ if (!s) {
+ ovs_fatal(0, "\"set\" command asks for nonexistent "
+ "i=%d", atoi(arg1));
+ }
+ idltest_simple_delete(s);
+ } else if (!strcmp(name, "increment")) {
+ if (!arg2 || arg3) {
+ ovs_fatal(0, "\"set\" command requires 2 arguments");
+ }
+ ovsdb_idl_txn_increment(txn, arg1, arg2, NULL);
+ increment = true;
+ } else {
+ ovs_fatal(0, "unknown command %s", name);
+ }
+ }
+
+ while ((status = ovsdb_idl_txn_commit(txn)) == TXN_INCOMPLETE) {
+ ovsdb_idl_run(idl);
+ ovsdb_idl_wait(idl);
+ ovsdb_idl_txn_wait(txn);
+ poll_block();
+ }
+ printf("%03d: commit, status=%s",
+ step, ovsdb_idl_txn_status_to_string(status));
+ if (increment) {
+ printf(", increment=%"PRId64,
+ ovsdb_idl_txn_get_increment_new_value(txn));
+ }
+ putchar('\n');
+ ovsdb_idl_txn_destroy(txn);
+}
+
+static void
+do_idl(int argc, char *argv[])
+{
+ struct jsonrpc *rpc;
+ struct ovsdb_idl *idl;
+ unsigned int seqno = 0;
+ struct ovsdb_symbol_table *symtab;
+ size_t n_uuids = 0;
+ int step = 0;
+ int error;
+ int i;
+
+ idltest_init();
+
+ idl = ovsdb_idl_create(argv[1], &idltest_idl_class);
+ if (argc > 2) {
+ struct stream *stream;
+
+ error = stream_open_block(argv[1], &stream);
+ if (error) {
+ ovs_fatal(error, "failed to connect to \"%s\"", argv[1]);
+ }
+ rpc = jsonrpc_open(stream);
+ } else {
+ rpc = NULL;
+ }
+
+ setvbuf(stdout, NULL, _IOLBF, 0);
+
+ symtab = ovsdb_symbol_table_create();
+ for (i = 2; i < argc; i++) {
+ char *arg = argv[i];
+ struct jsonrpc_msg *request, *reply;
+ int error;
+
+ if (*arg == '+') {
+ /* The previous transaction didn't change anything. */
+ arg++;
+ } else {
+ seqno = print_updated_idl(idl, rpc, step++, seqno);
+ }
+
+ if (!strcmp(arg, "reconnect")) {
+ printf("%03d: reconnect\n", step++);
+ ovsdb_idl_force_reconnect(idl);
+ } else if (arg[0] != '[') {
+ idl_set(idl, arg, step++);
+ } else {
+ struct json *json = parse_json(arg);
+ substitute_uuids(json, symtab);
+ request = jsonrpc_create_request("transact", json, NULL);
+ error = jsonrpc_transact_block(rpc, request, &reply);
+ if (error) {
+ ovs_fatal(error, "jsonrpc transaction failed");
+ }
+ printf("%03d: ", step++);
+ if (reply->result) {
+ parse_uuids(reply->result, symtab, &n_uuids);
+ }
+ json_destroy(reply->id);
+ reply->id = NULL;
+ print_and_free_json(jsonrpc_msg_to_json(reply));
+ }
+ }
+ ovsdb_symbol_table_destroy(symtab);
+
+ if (rpc) {
+ jsonrpc_close(rpc);
+ }
+ print_updated_idl(idl, NULL, step++, seqno);
+ ovsdb_idl_destroy(idl);
+ printf("%03d: done\n", step);
+}
+
+static struct command all_commands[] = {
+ { "log-io", 2, INT_MAX, do_log_io },
+ { "parse-atomic-type", 1, 1, do_parse_atomic_type },
+ { "parse-base-type", 1, 1, do_parse_base_type },
+ { "parse-type", 1, 1, do_parse_type },
+ { "parse-atoms", 2, INT_MAX, do_parse_atoms },
+ { "parse-atom-strings", 2, INT_MAX, do_parse_atom_strings },
+ { "parse-data", 2, INT_MAX, do_parse_data },
+ { "parse-data-strings", 2, INT_MAX, do_parse_data_strings },
+ { "sort-atoms", 2, 2, do_sort_atoms },
+ { "parse-column", 2, 2, do_parse_column },
+ { "parse-table", 2, 2, do_parse_table },
+ { "parse-rows", 2, INT_MAX, do_parse_rows },
+ { "compare-rows", 2, INT_MAX, do_compare_rows },
+ { "parse-conditions", 2, INT_MAX, do_parse_conditions },
+ { "evaluate-conditions", 3, 3, do_evaluate_conditions },
+ { "parse-mutations", 2, INT_MAX, do_parse_mutations },
+ { "execute-mutations", 3, 3, do_execute_mutations },
+ { "query", 3, 3, do_query },
+ { "query-distinct", 4, 4, do_query_distinct },
+ { "transact", 1, INT_MAX, do_transact },
+ { "parse-schema", 1, 1, do_parse_schema },
+ { "execute", 2, INT_MAX, do_execute },
+ { "trigger", 2, INT_MAX, do_trigger },
+ { "idl", 1, INT_MAX, do_idl },
+ { "help", 0, INT_MAX, do_help },
+ { NULL, 0, 0, NULL },
+};
--- /dev/null
- * Copyright (c) 2009 Nicira Networks.
+/*
- do_enable(int argc UNUSED, char *argv[] UNUSED)
++ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "reconnect.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "svec.h"
+#include "util.h"
+
+static struct reconnect *reconnect;
+static int now;
+
+static const struct command commands[];
+
+static void diff_stats(const struct reconnect_stats *old,
+ const struct reconnect_stats *new);
+
+int
+main(void)
+{
+ struct reconnect_stats prev;
+ unsigned int old_max_tries;
+ int old_time;
+ char line[128];
+
+ now = 1000;
+ reconnect = reconnect_create(now);
+ reconnect_set_name(reconnect, "remote");
+ reconnect_get_stats(reconnect, now, &prev);
+ printf("### t=%d ###\n", now);
+ old_time = now;
+ old_max_tries = reconnect_get_max_tries(reconnect);
+ while (fgets(line, sizeof line, stdin)) {
+ struct reconnect_stats cur;
+ struct svec args;
+
+ fputs(line, stdout);
+ if (line[0] == '#') {
+ continue;
+ }
+
+ svec_init(&args);
+ svec_parse_words(&args, line);
+ svec_terminate(&args);
+ if (!svec_is_empty(&args)) {
+ run_command(args.n, args.names, commands);
+ }
+ svec_destroy(&args);
+
+ if (old_time != now) {
+ printf("\n### t=%d ###\n", now);
+ old_time = now;
+ }
+
+ reconnect_get_stats(reconnect, now, &cur);
+ diff_stats(&prev, &cur);
+ prev = cur;
+ if (reconnect_get_max_tries(reconnect) != old_max_tries) {
+ old_max_tries = reconnect_get_max_tries(reconnect);
+ printf(" %u tries left\n", old_max_tries);
+ }
+ }
+
+ return 0;
+}
+
+static void
- do_disable(int argc UNUSED, char *argv[] UNUSED)
++do_enable(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ reconnect_enable(reconnect, now);
+}
+
+static void
- do_force_reconnect(int argc UNUSED, char *argv[] UNUSED)
++do_disable(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ reconnect_disable(reconnect, now);
+}
+
+static void
- do_disconnected(int argc UNUSED, char *argv[])
++do_force_reconnect(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ reconnect_force_reconnect(reconnect, now);
+}
+
+static int
+error_from_string(const char *s)
+{
+ if (!s) {
+ return 0;
+ } else if (!strcmp(s, "ECONNREFUSED")) {
+ return ECONNREFUSED;
+ } else if (!strcmp(s, "EOF")) {
+ return EOF;
+ } else {
+ ovs_fatal(0, "unknown error '%s'", s);
+ }
+}
+
+static void
- do_connecting(int argc UNUSED, char *argv[] UNUSED)
++do_disconnected(int argc OVS_UNUSED, char *argv[])
+{
+ reconnect_disconnected(reconnect, now, error_from_string(argv[1]));
+}
+
+static void
- do_connect_failed(int argc UNUSED, char *argv[])
++do_connecting(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ reconnect_connecting(reconnect, now);
+}
+
+static void
- do_connected(int argc UNUSED, char *argv[] UNUSED)
++do_connect_failed(int argc OVS_UNUSED, char *argv[])
+{
+ reconnect_connect_failed(reconnect, now, error_from_string(argv[1]));
+}
+
+static void
- do_received(int argc UNUSED, char *argv[] UNUSED)
++do_connected(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ reconnect_connected(reconnect, now);
+}
+
+static void
- do_advance(int argc UNUSED, char *argv[])
++do_received(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ reconnect_received(reconnect, now);
+}
+
+static void
+do_run(int argc, char *argv[])
+{
+ enum reconnect_action action;
+
+ if (argc > 1) {
+ now += atoi(argv[1]);
+ }
+
+ action = reconnect_run(reconnect, now);
+ switch (action) {
+ default:
+ if (action != 0) {
+ NOT_REACHED();
+ }
+ break;
+
+ case RECONNECT_CONNECT:
+ printf(" should connect\n");
+ break;
+
+ case RECONNECT_DISCONNECT:
+ printf(" should disconnect\n");
+ break;
+
+ case RECONNECT_PROBE:
+ printf(" should send probe\n");
+ break;
+ }
+}
+
+static void
- do_timeout(int argc UNUSED, char *argv[] UNUSED)
++do_advance(int argc OVS_UNUSED, char *argv[])
+{
+ now += atoi(argv[1]);
+}
+
+static void
- do_set_max_tries(int argc UNUSED, char *argv[])
++do_timeout(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ int timeout = reconnect_timeout(reconnect, now);
+ if (timeout >= 0) {
+ printf(" advance %d ms\n", timeout);
+ now += timeout;
+ } else {
+ printf(" no timeout\n");
+ }
+}
+
+static void
++do_set_max_tries(int argc OVS_UNUSED, char *argv[])
+{
+ reconnect_set_max_tries(reconnect, atoi(argv[1]));
+}
+
+static void
+diff_stats(const struct reconnect_stats *old,
+ const struct reconnect_stats *new)
+{
+ if (old->state != new->state
+ || old->state_elapsed != new->state_elapsed
+ || old->backoff != new->backoff) {
+ printf(" in %s for %u ms (%d ms backoff)\n",
+ new->state, new->state_elapsed, new->backoff);
+ }
+ if (old->creation_time != new->creation_time
+ || old->last_received != new->last_received
+ || old->last_connected != new->last_connected) {
+ printf(" created %lld, last received %lld, last connected %lld\n",
+ new->creation_time, new->last_received, new->last_connected);
+ }
+ if (old->n_successful_connections != new->n_successful_connections
+ || old->n_attempted_connections != new->n_attempted_connections
+ || old->seqno != new->seqno) {
+ printf(" %u successful connections out of %u attempts, seqno %u\n",
+ new->n_successful_connections, new->n_attempted_connections,
+ new->seqno);
+ }
+ if (old->is_connected != new->is_connected
+ || old->current_connection_duration != new->current_connection_duration
+ || old->total_connected_duration != new->total_connected_duration) {
+ printf(" %sconnected (%u ms), total %u ms connected\n",
+ new->is_connected ? "" : "not ",
+ new->current_connection_duration,
+ new->total_connected_duration);
+ }
+}
+
+static const struct command commands[] = {
+ { "enable", 0, 0, do_enable },
+ { "disable", 0, 0, do_disable },
+ { "force-reconnect", 0, 0, do_force_reconnect },
+ { "disconnected", 0, 1, do_disconnected },
+ { "connecting", 0, 0, do_connecting },
+ { "connect-failed", 0, 1, do_connect_failed },
+ { "connected", 0, 0, do_connected },
+ { "received", 0, 0, do_received },
+ { "run", 0, 1, do_run },
+ { "advance", 1, 1, do_advance },
+ { "timeout", 0, 0, do_timeout },
+ { "set-max-tries", 1, 1, do_set_max_tries },
+ { NULL, 0, 0, NULL },
+};
+
/* Connects to a fake_pvconn with vconn_open(), then closes the listener and
* verifies that vconn_connect() reports 'expected_error'. */
static void
- test_refuse_connection(int argc UNUSED, char *argv[])
-test_refuse_connection(const char *type, int expected_error)
++test_refuse_connection(int argc OVS_UNUSED, char *argv[])
{
+ const char *type = argv[1];
+ int expected_error;
struct fake_pvconn fpv;
struct vconn *vconn;
* closes it immediately, and verifies that vconn_connect() reports
* 'expected_error'. */
static void
- test_accept_then_close(int argc UNUSED, char *argv[])
-test_accept_then_close(const char *type, int expected_error)
++test_accept_then_close(int argc OVS_UNUSED, char *argv[])
{
+ const char *type = argv[1];
+ int expected_error;
struct fake_pvconn fpv;
struct vconn *vconn;
* reads the hello message from it, then closes the connection and verifies
* that vconn_connect() reports 'expected_error'. */
static void
- test_read_hello(int argc UNUSED, char *argv[])
-test_read_hello(const char *type, int expected_error)
++test_read_hello(int argc OVS_UNUSED, char *argv[])
{
+ const char *type = argv[1];
struct fake_pvconn fpv;
struct vconn *vconn;
- int fd;
+ struct stream *stream;
fpv_create(type, &fpv);
- assert(!vconn_open(fpv.vconn_name, OFP_VERSION, &vconn));
- fd = fpv_accept(&fpv);
+ CHECK_ERRNO(vconn_open(fpv.vconn_name, OFP_VERSION, &vconn), 0);
+ vconn_run(vconn);
+ stream = fpv_accept(&fpv);
fpv_destroy(&fpv);
- assert(!set_nonblocking(fd));
for (;;) {
struct ofp_header hello;
int retval;
/* Try connecting and sending a normal hello, which should succeed. */
static void
- test_send_plain_hello(int argc UNUSED, char *argv[])
-test_send_plain_hello(const char *type)
++test_send_plain_hello(int argc OVS_UNUSED, char *argv[])
{
+ const char *type = argv[1];
struct ofp_header hello;
hello.version = OFP_VERSION;
* the specification says that implementations must accept and ignore extra
* data). */
static void
- test_send_long_hello(int argc UNUSED, char *argv[])
-test_send_long_hello(const char *type)
++test_send_long_hello(int argc OVS_UNUSED, char *argv[])
{
+ const char *type = argv[1];
struct ofp_header hello;
char buffer[sizeof hello * 2];
/* Try connecting and sending an echo request instead of a hello, which should
* fail with EPROTO. */
static void
- test_send_echo_hello(int argc UNUSED, char *argv[])
-test_send_echo_hello(const char *type)
++test_send_echo_hello(int argc OVS_UNUSED, char *argv[])
{
+ const char *type = argv[1];
struct ofp_header echo;
echo.version = OFP_VERSION;
/* Try connecting and sending a hello packet that has its length field as 0,
* which should fail with EPROTO. */
static void
- test_send_short_hello(int argc UNUSED, char *argv[])
-test_send_short_hello(const char *type)
++test_send_short_hello(int argc OVS_UNUSED, char *argv[])
{
+ const char *type = argv[1];
struct ofp_header hello;
memset(&hello, 0, sizeof hello);
/* Try connecting and sending a hello packet that has a bad version, which
* should fail with EPROTO. */
static void
- test_send_invalid_version_hello(int argc UNUSED, char *argv[])
-test_send_invalid_version_hello(const char *type)
++test_send_invalid_version_hello(int argc OVS_UNUSED, char *argv[])
{
+ const char *type = argv[1];
struct ofp_header hello;
hello.version = OFP_VERSION - 1;
test_send_hello(type, &hello, sizeof hello, EPROTO);
}
+static const struct command commands[] = {
+ {"refuse-connection", 1, 1, test_refuse_connection},
+ {"accept-then-close", 1, 1, test_accept_then_close},
+ {"read-hello", 1, 1, test_read_hello},
+ {"send-plain-hello", 1, 1, test_send_plain_hello},
+ {"send-long-hello", 1, 1, test_send_long_hello},
+ {"send-echo-hello", 1, 1, test_send_echo_hello},
+ {"send-short-hello", 1, 1, test_send_short_hello},
+ {"send-invalid-version-hello", 1, 1, test_send_invalid_version_hello},
+ {NULL, 0, 0, NULL},
+};
+
int
- main(int argc, char *argv[])
+ main(int argc OVS_UNUSED, char *argv[])
{
set_program_name(argv[0]);
time_init();
return retval;
}
+static int
+parsed_dpif_open(const char *arg_, bool create, struct dpif **dpifp)
+{
+ int result;
+ char *name, *type;
+
+ dp_parse_name(arg_, &name, &type);
+
+ if (create) {
+ result = dpif_create(name, type, dpifp);
+ } else {
+ result = dpif_open(name, type, dpifp);
+ }
+
+ free(name);
+ free(type);
+ return result;
+}
+
static void
- do_add_dp(int argc UNUSED, char *argv[])
+ do_add_dp(int argc OVS_UNUSED, char *argv[])
{
struct dpif *dpif;
- run(dpif_create(argv[1], &dpif), "add_dp");
+ run(parsed_dpif_open(argv[1], true, &dpif), "add_dp");
dpif_close(dpif);
if (argc > 2) {
do_add_if(argc, argv);
}
static void
- do_del_dp(int argc UNUSED, char *argv[])
+ do_del_dp(int argc OVS_UNUSED, char *argv[])
{
struct dpif *dpif;
- run(dpif_open(argv[1], &dpif), "opening datapath");
+ run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
run(dpif_delete(dpif), "del_dp");
dpif_close(dpif);
}
}
static void
- do_dump_dps(int argc UNUSED, char *argv[] UNUSED)
+ do_dump_dps(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
{
- struct svec all_dps;
+ struct svec dpif_names, dpif_types;
unsigned int i;
- int error;
+ int error = 0;
+
+ svec_init(&dpif_names);
+ svec_init(&dpif_types);
+ dp_enumerate_types(&dpif_types);
+
+ for (i = 0; i < dpif_types.n; i++) {
+ unsigned int j;
+ int retval;
- svec_init(&all_dps);
- error = dp_enumerate(&all_dps);
+ retval = dp_enumerate_names(dpif_types.names[i], &dpif_names);
+ if (retval) {
+ error = retval;
+ }
- for (i = 0; i < all_dps.n; i++) {
- struct dpif *dpif;
- if (!dpif_open(all_dps.names[i], &dpif)) {
- printf("%s\n", dpif_name(dpif));
- dpif_close(dpif);
+ for (j = 0; j < dpif_names.n; j++) {
+ struct dpif *dpif;
+ if (!dpif_open(dpif_names.names[j], dpif_types.names[i], &dpif)) {
+ printf("%s\n", dpif_name(dpif));
+ dpif_close(dpif);
+ }
}
}
}
static void
- do_show(int argc UNUSED, char *argv[])
-do_show(const struct settings *s OVS_UNUSED, int argc OVS_UNUSED, char *argv[])
++do_show(int argc OVS_UNUSED, char *argv[])
{
dump_trivial_transaction(argv[1], OFPT_FEATURES_REQUEST);
dump_trivial_transaction(argv[1], OFPT_GET_CONFIG_REQUEST);
}
static void
- do_dump_desc(int argc UNUSED, char *argv[])
-do_dump_desc(const struct settings *s OVS_UNUSED,
- int argc OVS_UNUSED, char *argv[])
++do_dump_desc(int argc OVS_UNUSED, char *argv[])
{
dump_trivial_stats_transaction(argv[1], OFPST_DESC);
}
static void
- do_dump_tables(int argc UNUSED, char *argv[])
-do_dump_tables(const struct settings *s OVS_UNUSED,
- int argc OVS_UNUSED, char *argv[])
++do_dump_tables(int argc OVS_UNUSED, char *argv[])
{
dump_trivial_stats_transaction(argv[1], OFPST_TABLE);
}
}
static void
- do_add_flow(int argc UNUSED, char *argv[])
-do_add_flow(const struct settings *s OVS_UNUSED, int argc OVS_UNUSED,
- char *argv[])
++do_add_flow(int argc OVS_UNUSED, char *argv[])
{
struct vconn *vconn;
struct ofpbuf *buffer;
}
static void
- do_add_flows(int argc UNUSED, char *argv[])
-do_add_flows(const struct settings *s OVS_UNUSED, int argc OVS_UNUSED,
- char *argv[])
++do_add_flows(int argc OVS_UNUSED, char *argv[])
{
struct vconn *vconn;
FILE *file;
}
static void
- do_mod_flows(int argc UNUSED, char *argv[])
-do_mod_flows(const struct settings *s, int argc OVS_UNUSED, char *argv[])
++do_mod_flows(int argc OVS_UNUSED, char *argv[])
{
uint16_t priority, idle_timeout, hard_timeout;
struct vconn *vconn;
}
static void
- do_monitor(int argc UNUSED, char *argv[])
-do_monitor(const struct settings *s OVS_UNUSED,
- int argc OVS_UNUSED, char *argv[])
++do_monitor(int argc OVS_UNUSED, char *argv[])
{
struct vconn *vconn;
}
static void
- do_dump_ports(int argc UNUSED, char *argv[])
-do_dump_ports(const struct settings *s OVS_UNUSED,
- int argc OVS_UNUSED, char *argv[])
++do_dump_ports(int argc OVS_UNUSED, char *argv[])
{
dump_trivial_stats_transaction(argv[1], OFPST_PORT);
}
static void
- do_probe(int argc UNUSED, char *argv[])
-do_probe(const struct settings *s OVS_UNUSED,
- int argc OVS_UNUSED, char *argv[])
++do_probe(int argc OVS_UNUSED, char *argv[])
{
struct ofpbuf *request;
struct vconn *vconn;
}
static void
- do_mod_port(int argc UNUSED, char *argv[])
-do_mod_port(const struct settings *s OVS_UNUSED,
- int argc OVS_UNUSED, char *argv[])
++do_mod_port(int argc OVS_UNUSED, char *argv[])
{
struct ofpbuf *request, *reply;
struct ofp_switch_features *osf;
}
static void
- do_benchmark(int argc UNUSED, char *argv[])
-do_benchmark(const struct settings *s OVS_UNUSED,
- int argc OVS_UNUSED, char *argv[])
++do_benchmark(int argc OVS_UNUSED, char *argv[])
{
size_t max_payload = 65535 - sizeof(struct ofp_header);
struct timeval start, end;
}
static void
- do_help(int argc UNUSED, char *argv[] UNUSED)
-do_execute(const struct settings *s OVS_UNUSED, int argc, char *argv[])
-{
- struct vconn *vconn;
- struct ofpbuf *request;
- struct nicira_header *nicira;
- struct nx_command_reply *ncr;
- uint32_t xid;
- int i;
-
- nicira = make_openflow(sizeof *nicira, OFPT_VENDOR, &request);
- xid = nicira->header.xid;
- nicira->vendor = htonl(NX_VENDOR_ID);
- nicira->subtype = htonl(NXT_COMMAND_REQUEST);
- ofpbuf_put(request, argv[2], strlen(argv[2]));
- for (i = 3; i < argc; i++) {
- ofpbuf_put_zeros(request, 1);
- ofpbuf_put(request, argv[i], strlen(argv[i]));
- }
- update_openflow_length(request);
-
- open_vconn(argv[1], &vconn);
- run(vconn_send_block(vconn, request), "send");
-
- for (;;) {
- struct ofpbuf *reply;
- uint32_t status;
-
- run(vconn_recv_xid(vconn, xid, &reply), "recv_xid");
- if (reply->size < sizeof *ncr) {
- ovs_fatal(0, "reply is too short (%zu bytes < %zu bytes)",
- reply->size, sizeof *ncr);
- }
- ncr = reply->data;
- if (ncr->nxh.header.type != OFPT_VENDOR
- || ncr->nxh.vendor != htonl(NX_VENDOR_ID)
- || ncr->nxh.subtype != htonl(NXT_COMMAND_REPLY)) {
- ovs_fatal(0, "reply is invalid");
- }
-
- status = ntohl(ncr->status);
- if (status & NXT_STATUS_STARTED) {
- /* Wait for a second reply. */
- continue;
- } else if (status & NXT_STATUS_EXITED) {
- fprintf(stderr, "process terminated normally with exit code %d",
- status & NXT_STATUS_EXITSTATUS);
- } else if (status & NXT_STATUS_SIGNALED) {
- fprintf(stderr, "process terminated by signal %d",
- status & NXT_STATUS_TERMSIG);
- } else if (status & NXT_STATUS_ERROR) {
- fprintf(stderr, "error executing command");
- } else {
- fprintf(stderr, "process terminated for unknown reason");
- }
- if (status & NXT_STATUS_COREDUMP) {
- fprintf(stderr, " (core dumped)");
- }
- putc('\n', stderr);
-
- fwrite(ncr + 1, reply->size - sizeof *ncr, 1, stdout);
- break;
- }
-}
-
-static void
-do_help(const struct settings *s OVS_UNUSED,
- int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
++do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
{
usage();
}
--- /dev/null
- cmd_init(struct vsctl_context *ctx UNUSED)
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "dirs.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "ovsdb-data.h"
+#include "ovsdb-idl.h"
+#include "poll-loop.h"
+#include "process.h"
+#include "svec.h"
+#include "vswitchd/vswitch-idl.h"
+#include "timeval.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_vsctl
+
+/* vsctl_fatal() also logs the error, so it is preferred in this file. */
+#define ovs_fatal please_use_vsctl_fatal_instead_of_ovs_fatal
+
+struct vsctl_context;
+
+typedef void vsctl_handler_func(struct vsctl_context *);
+
+struct vsctl_command_syntax {
+ const char *name;
+ int min_args;
+ int max_args;
+ vsctl_handler_func *run;
+ vsctl_handler_func *postprocess;
+ const char *options;
+};
+
+struct vsctl_command {
+ /* Data that remains constant after initialization. */
+ const struct vsctl_command_syntax *syntax;
+ int argc;
+ char **argv;
+ struct shash options;
+
+ /* Data modified by commands. */
+ struct ds output;
+};
+
+/* --db: The database server to contact. */
+static const char *db;
+
+/* --oneline: Write each command's output as a single line? */
+static bool oneline;
+
+/* --dry-run: Do not commit any changes. */
+static bool dry_run;
+
+/* --no-wait: Wait for ovs-vswitchd to reload its configuration? */
+static bool wait_for_reload = true;
+
+/* --timeout: Time to wait for a connection to 'db'. */
+static int timeout = 5;
+
+/* All supported commands. */
+static const struct vsctl_command_syntax all_commands[];
+
+/* The IDL we're using and the current transaction, if any.
+ * This is for use by vsctl_exit() only, to allow it to clean up.
+ * Other code should use its context arguments. */
+static struct ovsdb_idl *the_idl;
+static struct ovsdb_idl_txn *the_idl_txn;
+
+static void vsctl_exit(int status) NO_RETURN;
+static void vsctl_fatal(const char *, ...) PRINTF_FORMAT(1, 2) NO_RETURN;
+static char *default_db(void);
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+static struct vsctl_command *parse_commands(int argc, char *argv[],
+ size_t *n_commandsp);
+static void parse_command(int argc, char *argv[], struct vsctl_command *);
+static void do_vsctl(const char *args,
+ struct vsctl_command *, size_t n_commands,
+ struct ovsdb_idl *);
+
+int
+main(int argc, char *argv[])
+{
+ struct ovsdb_idl *idl;
+ unsigned int seqno;
+ struct vsctl_command *commands;
+ size_t n_commands;
+ char *args;
+ int trials;
+
+ set_program_name(argv[0]);
+ signal(SIGPIPE, SIG_IGN);
+ time_init();
+ vlog_init();
+ vlog_set_levels(VLM_ANY_MODULE, VLF_CONSOLE, VLL_WARN);
+ vlog_set_levels(VLM_reconnect, VLF_ANY_FACILITY, VLL_WARN);
+ ovsrec_init();
+
+ /* Log our arguments. This is often valuable for debugging systems. */
+ args = process_escape_args(argv);
+ VLOG_INFO("Called as %s", args);
+
+ /* Parse command line. */
+ parse_options(argc, argv);
+ commands = parse_commands(argc - optind, argv + optind, &n_commands);
+
+ if (timeout) {
+ time_alarm(timeout);
+ }
+
+ /* Now execute the commands. */
+ idl = the_idl = ovsdb_idl_create(db, &ovsrec_idl_class);
+ seqno = ovsdb_idl_get_seqno(idl);
+ trials = 0;
+ for (;;) {
+ unsigned int new_seqno;
+
+ ovsdb_idl_run(idl);
+ new_seqno = ovsdb_idl_get_seqno(idl);
+ if (new_seqno != seqno) {
+ if (++trials > 5) {
+ vsctl_fatal("too many database inconsistency failures");
+ }
+ do_vsctl(args, commands, n_commands, idl);
+ seqno = new_seqno;
+ }
+
+ ovsdb_idl_wait(idl);
+ poll_block();
+ }
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+ enum {
+ OPT_DB = UCHAR_MAX + 1,
+ OPT_ONELINE,
+ OPT_NO_SYSLOG,
+ OPT_NO_WAIT,
+ OPT_DRY_RUN,
+ VLOG_OPTION_ENUMS
+ };
+ static struct option long_options[] = {
+ {"db", required_argument, 0, OPT_DB},
+ {"no-syslog", no_argument, 0, OPT_NO_SYSLOG},
+ {"no-wait", no_argument, 0, OPT_NO_WAIT},
+ {"dry-run", no_argument, 0, OPT_DRY_RUN},
+ {"oneline", no_argument, 0, OPT_ONELINE},
+ {"timeout", required_argument, 0, 't'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ VLOG_LONG_OPTIONS,
+ {0, 0, 0, 0},
+ };
+
+
+ for (;;) {
+ int c;
+
+ c = getopt_long(argc, argv, "+v::hVt:", long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case OPT_DB:
+ db = optarg;
+ break;
+
+ case OPT_ONELINE:
+ oneline = true;
+ break;
+
+ case OPT_NO_SYSLOG:
+ vlog_set_levels(VLM_vsctl, VLF_SYSLOG, VLL_WARN);
+ break;
+
+ case OPT_NO_WAIT:
+ wait_for_reload = false;
+ break;
+
+ case OPT_DRY_RUN:
+ dry_run = true;
+ break;
+
+ case 'h':
+ usage();
+
+ case 'V':
+ OVS_PRINT_VERSION(0, 0);
+ exit(EXIT_SUCCESS);
+
+ case 't':
+ timeout = strtoul(optarg, NULL, 10);
+ if (timeout < 0) {
+ vsctl_fatal("value %s on -t or --timeout is invalid",
+ optarg);
+ }
+ break;
+
+ VLOG_OPTION_HANDLERS
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+
+ if (!db) {
+ db = default_db();
+ }
+}
+
+static struct vsctl_command *
+parse_commands(int argc, char *argv[], size_t *n_commandsp)
+{
+ struct vsctl_command *commands;
+ size_t n_commands, allocated_commands;
+ int i, start;
+
+ commands = NULL;
+ n_commands = allocated_commands = 0;
+
+ for (start = i = 0; i <= argc; i++) {
+ if (i == argc || !strcmp(argv[i], "--")) {
+ if (i > start) {
+ if (n_commands >= allocated_commands) {
+ struct vsctl_command *c;
+
+ commands = x2nrealloc(commands, &allocated_commands,
+ sizeof *commands);
+ for (c = commands; c < &commands[n_commands]; c++) {
+ shash_moved(&c->options);
+ }
+ }
+ parse_command(i - start, &argv[start],
+ &commands[n_commands++]);
+ }
+ start = i + 1;
+ }
+ }
+ if (!n_commands) {
+ vsctl_fatal("missing command name (use --help for help)");
+ }
+ *n_commandsp = n_commands;
+ return commands;
+}
+
+static void
+parse_command(int argc, char *argv[], struct vsctl_command *command)
+{
+ const struct vsctl_command_syntax *p;
+ int i;
+
+ shash_init(&command->options);
+ for (i = 0; i < argc; i++) {
+ if (argv[i][0] != '-') {
+ break;
+ }
+ if (!shash_add_once(&command->options, argv[i], NULL)) {
+ vsctl_fatal("'%s' option specified multiple times", argv[i]);
+ }
+ }
+ if (i == argc) {
+ vsctl_fatal("missing command name");
+ }
+
+ for (p = all_commands; p->name; p++) {
+ if (!strcmp(p->name, argv[i])) {
+ struct shash_node *node;
+ int n_arg;
+
+ SHASH_FOR_EACH (node, &command->options) {
+ const char *s = strstr(p->options, node->name);
+ int end = s ? s[strlen(node->name)] : EOF;
+ if (end != ',' && end != ' ' && end != '\0') {
+ vsctl_fatal("'%s' command has no '%s' option",
+ argv[i], node->name);
+ }
+ }
+
+ n_arg = argc - i - 1;
+ if (n_arg < p->min_args) {
+ vsctl_fatal("'%s' command requires at least %d arguments",
+ p->name, p->min_args);
+ } else if (n_arg > p->max_args) {
+ int j;
+
+ for (j = i + 1; j < argc; j++) {
+ if (argv[j][0] == '-') {
+ vsctl_fatal("'%s' command takes at most %d arguments "
+ "(note that options must precede command "
+ "names and follow a \"--\" argument)",
+ p->name, p->max_args);
+ }
+ }
+
+ vsctl_fatal("'%s' command takes at most %d arguments",
+ p->name, p->max_args);
+ } else {
+ command->syntax = p;
+ command->argc = n_arg + 1;
+ command->argv = &argv[i];
+ return;
+ }
+ }
+ }
+
+ vsctl_fatal("unknown command '%s'; use --help for help", argv[i]);
+}
+
+static void
+vsctl_fatal(const char *format, ...)
+{
+ char *message;
+ va_list args;
+
+ va_start(args, format);
+ message = xvasprintf(format, args);
+ va_end(args);
+
+ vlog_set_levels(VLM_vsctl, VLF_CONSOLE, VLL_EMER);
+ VLOG_ERR("%s", message);
+ ovs_error(0, "%s", message);
+ vsctl_exit(EXIT_FAILURE);
+}
+
+/* Frees the current transaction and the underlying IDL and then calls
+ * exit(status).
+ *
+ * Freeing the transaction and the IDL is not strictly necessary, but it makes
+ * for a clean memory leak report from valgrind in the normal case. That makes
+ * it easier to notice real memory leaks. */
+static void
+vsctl_exit(int status)
+{
+ if (the_idl_txn) {
+ ovsdb_idl_txn_abort(the_idl_txn);
+ ovsdb_idl_txn_destroy(the_idl_txn);
+ }
+ ovsdb_idl_destroy(the_idl);
+ exit(status);
+}
+
+static void
+usage(void)
+{
+ printf("\
+%s: ovs-vswitchd management utility\n\
+usage: %s [OPTIONS] COMMAND [ARG...]\n\
+\n\
+Bridge commands:\n\
+ add-br BRIDGE create a new bridge named BRIDGE\n\
+ add-br BRIDGE PARENT VLAN create new fake BRIDGE in PARENT on VLAN\n\
+ del-br BRIDGE delete BRIDGE and all of its ports\n\
+ list-br print the names of all the bridges\n\
+ br-exists BRIDGE test whether BRIDGE exists\n\
+ br-to-vlan BRIDGE print the VLAN which BRIDGE is on\n\
+ br-to-parent BRIDGE print the parent of BRIDGE\n\
+ br-set-external-id BRIDGE KEY VALUE set KEY on BRIDGE to VALUE\n\
+ br-set-external-id BRIDGE KEY unset KEY on BRIDGE\n\
+ br-get-external-id BRIDGE KEY print value of KEY on BRIDGE\n\
+ br-get-external-id BRIDGE list key-value pairs on BRIDGE\n\
+\n\
+Port commands:\n\
+ list-ports BRIDGE print the names of all the ports on BRIDGE\n\
+ add-port BRIDGE PORT add network device PORT to BRIDGE\n\
+ add-bond BRIDGE PORT IFACE... add bonded port PORT in BRIDGE from IFACES\n\
+ del-port [BRIDGE] PORT delete PORT (which may be bonded) from BRIDGE\n\
+ port-to-br PORT print name of bridge that contains PORT\n\
+A bond is considered to be a single port.\n\
+\n\
+Interface commands (a bond consists of multiple interfaces):\n\
+ list-ifaces BRIDGE print the names of all interfaces on BRIDGE\n\
+ iface-to-br IFACE print name of bridge that contains IFACE\n\
+\n\
+Controller commands:\n\
+ get-controller [BRIDGE] print the controller for BRIDGE\n\
+ del-controller [BRIDGE] delete the controller for BRIDGE\n\
+ set-controller [BRIDGE] TARGET set the controller for BRIDGE to TARGET\n\
+ get-fail-mode [BRIDGE] print the fail-mode for BRIDGE\n\
+ del-fail-mode [BRIDGE] delete the fail-mode for BRIDGE\n\
+ set-fail-mode [BRIDGE] MODE set the fail-mode for BRIDGE to MODE\n\
+\n\
+SSL commands:\n\
+ get-ssl print the SSL configuration\n\
+ del-ssl delete the SSL configuration\n\
+ set-ssl PRIV-KEY CERT CA-CERT set the SSL configuration\n\
+\n\
+Database commands:\n\
+ list TBL [REC] list RECord (or all records) in TBL\n\
+ get TBL REC COL[:KEY] print values of COLumns in RECORD in TBL\n\
+ set TBL REC COL[:KEY]=VALUE set COLumn values in RECord in TBL\n\
+ add TBL REC COL [KEY=]VALUE add (KEY=)VALUE to COLumn in RECord in TBL\n\
+ remove TBL REC COL [KEY=]VALUE remove (KEY=)VALUE from COLumn\n\
+ clear TBL REC COL clear values from COLumn in RECord in TBL\n\
+ create TBL COL[:KEY]=VALUE create and initialize new record\n\
+ destroy TBL REC delete REC from TBL\n\
+Potentially unsafe database commands require --force option.\n\
+\n\
+Options:\n\
+ --db=DATABASE connect to DATABASE\n\
+ (default: %s)\n\
+ --oneline print exactly one line of output per command\n",
+ program_name, program_name, default_db());
+ vlog_usage();
+ printf("\n\
+Other options:\n\
+ -h, --help display this help message\n\
+ -V, --version display version information\n");
+ exit(EXIT_SUCCESS);
+}
+
+static char *
+default_db(void)
+{
+ static char *def;
+ if (!def) {
+ def = xasprintf("unix:%s/ovsdb-server", ovs_rundir);
+ }
+ return def;
+}
+\f
+struct vsctl_context {
+ /* Read-only. */
+ int argc;
+ char **argv;
+ struct shash options;
+
+ /* Modifiable state. */
+ struct ds output;
+ struct ovsdb_idl *idl;
+ struct ovsdb_idl_txn *txn;
+ const struct ovsrec_open_vswitch *ovs;
+};
+
+struct vsctl_bridge {
+ struct ovsrec_bridge *br_cfg;
+ char *name;
+ struct ovsrec_controller *ctrl;
+ struct vsctl_bridge *parent;
+ int vlan;
+};
+
+struct vsctl_port {
+ struct ovsrec_port *port_cfg;
+ struct vsctl_bridge *bridge;
+};
+
+struct vsctl_iface {
+ struct ovsrec_interface *iface_cfg;
+ struct vsctl_port *port;
+};
+
+struct vsctl_info {
+ struct shash bridges;
+ struct shash ports;
+ struct shash ifaces;
+ struct ovsrec_controller *ctrl;
+};
+
+static char *
+vsctl_context_to_string(const struct vsctl_context *ctx)
+{
+ const struct shash_node *node;
+ struct svec words;
+ char *s;
+ int i;
+
+ svec_init(&words);
+ SHASH_FOR_EACH (node, &ctx->options) {
+ svec_add(&words, node->name);
+ }
+ for (i = 0; i < ctx->argc; i++) {
+ svec_add(&words, ctx->argv[i]);
+ }
+ svec_terminate(&words);
+
+ s = process_escape_args(words.names);
+
+ svec_destroy(&words);
+
+ return s;
+}
+
+static struct vsctl_bridge *
+add_bridge(struct vsctl_info *b,
+ struct ovsrec_bridge *br_cfg, const char *name,
+ struct vsctl_bridge *parent, int vlan)
+{
+ struct vsctl_bridge *br = xmalloc(sizeof *br);
+ br->br_cfg = br_cfg;
+ br->name = xstrdup(name);
+ br->parent = parent;
+ br->vlan = vlan;
+ br->ctrl = parent ? parent->br_cfg->controller : br_cfg->controller;
+ shash_add(&b->bridges, br->name, br);
+ return br;
+}
+
+static bool
+port_is_fake_bridge(const struct ovsrec_port *port_cfg)
+{
+ return (port_cfg->fake_bridge
+ && port_cfg->tag
+ && *port_cfg->tag >= 1 && *port_cfg->tag <= 4095);
+}
+
+static struct vsctl_bridge *
+find_vlan_bridge(struct vsctl_info *info,
+ struct vsctl_bridge *parent, int vlan)
+{
+ struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &info->bridges) {
+ struct vsctl_bridge *br = node->data;
+ if (br->parent == parent && br->vlan == vlan) {
+ return br;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+free_info(struct vsctl_info *info)
+{
+ struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &info->bridges) {
+ struct vsctl_bridge *bridge = node->data;
+ free(bridge->name);
+ free(bridge);
+ }
+ shash_destroy(&info->bridges);
+
+ SHASH_FOR_EACH (node, &info->ports) {
+ struct vsctl_port *port = node->data;
+ free(port);
+ }
+ shash_destroy(&info->ports);
+
+ SHASH_FOR_EACH (node, &info->ifaces) {
+ struct vsctl_iface *iface = node->data;
+ free(iface);
+ }
+ shash_destroy(&info->ifaces);
+}
+
+static void
+get_info(const struct ovsrec_open_vswitch *ovs, struct vsctl_info *info)
+{
+ struct shash bridges, ports;
+ size_t i;
+
+ shash_init(&info->bridges);
+ shash_init(&info->ports);
+ shash_init(&info->ifaces);
+
+ info->ctrl = ovs->controller;
+
+ shash_init(&bridges);
+ shash_init(&ports);
+ for (i = 0; i < ovs->n_bridges; i++) {
+ struct ovsrec_bridge *br_cfg = ovs->bridges[i];
+ struct vsctl_bridge *br;
+ size_t j;
+
+ if (!shash_add_once(&bridges, br_cfg->name, NULL)) {
+ VLOG_WARN("%s: database contains duplicate bridge name",
+ br_cfg->name);
+ continue;
+ }
+ br = add_bridge(info, br_cfg, br_cfg->name, NULL, 0);
+ if (!br) {
+ continue;
+ }
+
+ for (j = 0; j < br_cfg->n_ports; j++) {
+ struct ovsrec_port *port_cfg = br_cfg->ports[j];
+
+ if (!shash_add_once(&ports, port_cfg->name, NULL)) {
+ VLOG_WARN("%s: database contains duplicate port name",
+ port_cfg->name);
+ continue;
+ }
+
+ if (port_is_fake_bridge(port_cfg)
+ && shash_add_once(&bridges, port_cfg->name, NULL)) {
+ add_bridge(info, NULL, port_cfg->name, br, *port_cfg->tag);
+ }
+ }
+ }
+ shash_destroy(&bridges);
+ shash_destroy(&ports);
+
+ shash_init(&bridges);
+ shash_init(&ports);
+ for (i = 0; i < ovs->n_bridges; i++) {
+ struct ovsrec_bridge *br_cfg = ovs->bridges[i];
+ struct vsctl_bridge *br;
+ size_t j;
+
+ if (!shash_add_once(&bridges, br_cfg->name, NULL)) {
+ continue;
+ }
+ br = shash_find_data(&info->bridges, br_cfg->name);
+ for (j = 0; j < br_cfg->n_ports; j++) {
+ struct ovsrec_port *port_cfg = br_cfg->ports[j];
+ struct vsctl_port *port;
+ size_t k;
+
+ if (!shash_add_once(&ports, port_cfg->name, NULL)) {
+ continue;
+ }
+
+ if (port_is_fake_bridge(port_cfg)
+ && !shash_add_once(&bridges, port_cfg->name, NULL)) {
+ continue;
+ }
+
+ port = xmalloc(sizeof *port);
+ port->port_cfg = port_cfg;
+ if (port_cfg->tag
+ && *port_cfg->tag >= 1 && *port_cfg->tag <= 4095) {
+ port->bridge = find_vlan_bridge(info, br, *port_cfg->tag);
+ if (!port->bridge) {
+ port->bridge = br;
+ }
+ } else {
+ port->bridge = br;
+ }
+ shash_add(&info->ports, port_cfg->name, port);
+
+ for (k = 0; k < port_cfg->n_interfaces; k++) {
+ struct ovsrec_interface *iface_cfg = port_cfg->interfaces[k];
+ struct vsctl_iface *iface;
+
+ if (shash_find(&info->ifaces, iface_cfg->name)) {
+ VLOG_WARN("%s: database contains duplicate interface name",
+ iface_cfg->name);
+ continue;
+ }
+
+ iface = xmalloc(sizeof *iface);
+ iface->iface_cfg = iface_cfg;
+ iface->port = port;
+ shash_add(&info->ifaces, iface_cfg->name, iface);
+ }
+ }
+ }
+ shash_destroy(&bridges);
+ shash_destroy(&ports);
+}
+
+static void
+check_conflicts(struct vsctl_info *info, const char *name,
+ char *msg)
+{
+ struct vsctl_iface *iface;
+ struct vsctl_port *port;
+
+ if (shash_find(&info->bridges, name)) {
+ vsctl_fatal("%s because a bridge named %s already exists",
+ msg, name);
+ }
+
+ port = shash_find_data(&info->ports, name);
+ if (port) {
+ vsctl_fatal("%s because a port named %s already exists on "
+ "bridge %s", msg, name, port->bridge->name);
+ }
+
+ iface = shash_find_data(&info->ifaces, name);
+ if (iface) {
+ vsctl_fatal("%s because an interface named %s already exists "
+ "on bridge %s", msg, name, iface->port->bridge->name);
+ }
+
+ free(msg);
+}
+
+static struct vsctl_bridge *
+find_bridge(struct vsctl_info *info, const char *name, bool must_exist)
+{
+ struct vsctl_bridge *br = shash_find_data(&info->bridges, name);
+ if (must_exist && !br) {
+ vsctl_fatal("no bridge named %s", name);
+ }
+ return br;
+}
+
+static struct vsctl_bridge *
+find_real_bridge(struct vsctl_info *info, const char *name, bool must_exist)
+{
+ struct vsctl_bridge *br = find_bridge(info, name, must_exist);
+ if (br && br->parent) {
+ vsctl_fatal("%s is a fake bridge", name);
+ }
+ return br;
+}
+
+static struct vsctl_port *
+find_port(struct vsctl_info *info, const char *name, bool must_exist)
+{
+ struct vsctl_port *port = shash_find_data(&info->ports, name);
+ if (port && !strcmp(name, port->bridge->name)) {
+ port = NULL;
+ }
+ if (must_exist && !port) {
+ vsctl_fatal("no port named %s", name);
+ }
+ return port;
+}
+
+static struct vsctl_iface *
+find_iface(struct vsctl_info *info, const char *name, bool must_exist)
+{
+ struct vsctl_iface *iface = shash_find_data(&info->ifaces, name);
+ if (iface && !strcmp(name, iface->port->bridge->name)) {
+ iface = NULL;
+ }
+ if (must_exist && !iface) {
+ vsctl_fatal("no interface named %s", name);
+ }
+ return iface;
+}
+
+static void
+bridge_insert_port(struct ovsrec_bridge *br, struct ovsrec_port *port)
+{
+ struct ovsrec_port **ports;
+ size_t i;
+
+ ports = xmalloc(sizeof *br->ports * (br->n_ports + 1));
+ for (i = 0; i < br->n_ports; i++) {
+ ports[i] = br->ports[i];
+ }
+ ports[br->n_ports] = port;
+ ovsrec_bridge_set_ports(br, ports, br->n_ports + 1);
+ free(ports);
+}
+
+static void
+bridge_delete_port(struct ovsrec_bridge *br, struct ovsrec_port *port)
+{
+ struct ovsrec_port **ports;
+ size_t i, n;
+
+ ports = xmalloc(sizeof *br->ports * br->n_ports);
+ for (i = n = 0; i < br->n_ports; i++) {
+ if (br->ports[i] != port) {
+ ports[n++] = br->ports[i];
+ }
+ }
+ ovsrec_bridge_set_ports(br, ports, n);
+ free(ports);
+}
+
+static void
+ovs_insert_bridge(const struct ovsrec_open_vswitch *ovs,
+ struct ovsrec_bridge *bridge)
+{
+ struct ovsrec_bridge **bridges;
+ size_t i;
+
+ bridges = xmalloc(sizeof *ovs->bridges * (ovs->n_bridges + 1));
+ for (i = 0; i < ovs->n_bridges; i++) {
+ bridges[i] = ovs->bridges[i];
+ }
+ bridges[ovs->n_bridges] = bridge;
+ ovsrec_open_vswitch_set_bridges(ovs, bridges, ovs->n_bridges + 1);
+ free(bridges);
+}
+
+static void
+ovs_delete_bridge(const struct ovsrec_open_vswitch *ovs,
+ struct ovsrec_bridge *bridge)
+{
+ struct ovsrec_bridge **bridges;
+ size_t i, n;
+
+ bridges = xmalloc(sizeof *ovs->bridges * ovs->n_bridges);
+ for (i = n = 0; i < ovs->n_bridges; i++) {
+ if (ovs->bridges[i] != bridge) {
+ bridges[n++] = ovs->bridges[i];
+ }
+ }
+ ovsrec_open_vswitch_set_bridges(ovs, bridges, n);
+ free(bridges);
+}
+
+static void
++cmd_init(struct vsctl_context *ctx OVS_UNUSED)
+{
+}
+
+static void
+cmd_add_br(struct vsctl_context *ctx)
+{
+ bool may_exist = shash_find(&ctx->options, "--may-exist") != 0;
+ const char *br_name, *parent_name;
+ struct vsctl_info info;
+ int vlan;
+
+ br_name = ctx->argv[1];
+ if (ctx->argc == 2) {
+ parent_name = NULL;
+ vlan = 0;
+ } else if (ctx->argc == 4) {
+ parent_name = ctx->argv[2];
+ vlan = atoi(ctx->argv[3]);
+ if (vlan < 1 || vlan > 4095) {
+ vsctl_fatal("%s: vlan must be between 1 and 4095", ctx->argv[0]);
+ }
+ } else {
+ vsctl_fatal("'%s' command takes exactly 1 or 3 arguments",
+ ctx->argv[0]);
+ }
+
+ get_info(ctx->ovs, &info);
+ if (may_exist) {
+ struct vsctl_bridge *br;
+
+ br = find_bridge(&info, br_name, false);
+ if (br) {
+ if (!parent_name) {
+ if (br->parent) {
+ vsctl_fatal("\"--may-exist add-br %s\" but %s is "
+ "a VLAN bridge for VLAN %d",
+ br_name, br_name, br->vlan);
+ }
+ } else {
+ if (!br->parent) {
+ vsctl_fatal("\"--may-exist add-br %s %s %d\" but %s "
+ "is not a VLAN bridge",
+ br_name, parent_name, vlan, br_name);
+ } else if (strcmp(br->parent->name, parent_name)) {
+ vsctl_fatal("\"--may-exist add-br %s %s %d\" but %s "
+ "has the wrong parent %s",
+ br_name, parent_name, vlan,
+ br_name, br->parent->name);
+ } else if (br->vlan != vlan) {
+ vsctl_fatal("\"--may-exist add-br %s %s %d\" but %s "
+ "is a VLAN bridge for the wrong VLAN %d",
+ br_name, parent_name, vlan, br_name, br->vlan);
+ }
+ }
+ return;
+ }
+ }
+ check_conflicts(&info, br_name,
+ xasprintf("cannot create a bridge named %s", br_name));
+
+ if (!parent_name) {
+ struct ovsrec_port *port;
+ struct ovsrec_interface *iface;
+ struct ovsrec_bridge *br;
+
+ iface = ovsrec_interface_insert(ctx->txn);
+ ovsrec_interface_set_name(iface, br_name);
+
+ port = ovsrec_port_insert(ctx->txn);
+ ovsrec_port_set_name(port, br_name);
+ ovsrec_port_set_interfaces(port, &iface, 1);
+
+ br = ovsrec_bridge_insert(ctx->txn);
+ ovsrec_bridge_set_name(br, br_name);
+ ovsrec_bridge_set_ports(br, &port, 1);
+
+ ovs_insert_bridge(ctx->ovs, br);
+ } else {
+ struct vsctl_bridge *parent;
+ struct ovsrec_port *port;
+ struct ovsrec_interface *iface;
+ struct ovsrec_bridge *br;
+ int64_t tag = vlan;
+
+ parent = find_bridge(&info, parent_name, false);
+ if (parent && parent->vlan) {
+ vsctl_fatal("cannot create bridge with fake bridge as parent");
+ }
+ if (!parent) {
+ vsctl_fatal("parent bridge %s does not exist", parent_name);
+ }
+ br = parent->br_cfg;
+
+ iface = ovsrec_interface_insert(ctx->txn);
+ ovsrec_interface_set_name(iface, br_name);
+ ovsrec_interface_set_type(iface, "internal");
+
+ port = ovsrec_port_insert(ctx->txn);
+ ovsrec_port_set_name(port, br_name);
+ ovsrec_port_set_interfaces(port, &iface, 1);
+ ovsrec_port_set_fake_bridge(port, true);
+ ovsrec_port_set_tag(port, &tag, 1);
+
+ bridge_insert_port(br, port);
+ }
+
+ free_info(&info);
+}
+
+static void
+del_port(struct vsctl_info *info, struct vsctl_port *port)
+{
+ struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &info->ifaces) {
+ struct vsctl_iface *iface = node->data;
+ if (iface->port == port) {
+ ovsrec_interface_delete(iface->iface_cfg);
+ }
+ }
+ ovsrec_port_delete(port->port_cfg);
+
+ bridge_delete_port((port->bridge->parent
+ ? port->bridge->parent->br_cfg
+ : port->bridge->br_cfg), port->port_cfg);
+}
+
+static void
+cmd_del_br(struct vsctl_context *ctx)
+{
+ bool must_exist = !shash_find(&ctx->options, "--if-exists");
+ struct vsctl_bridge *bridge;
+ struct vsctl_info info;
+
+ get_info(ctx->ovs, &info);
+ bridge = find_bridge(&info, ctx->argv[1], must_exist);
+ if (bridge) {
+ struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &info.ports) {
+ struct vsctl_port *port = node->data;
+ if (port->bridge == bridge || port->bridge->parent == bridge
+ || !strcmp(port->port_cfg->name, bridge->name)) {
+ del_port(&info, port);
+ }
+ }
+ if (bridge->br_cfg) {
+ ovsrec_bridge_delete(bridge->br_cfg);
+ ovs_delete_bridge(ctx->ovs, bridge->br_cfg);
+ }
+ }
+ free_info(&info);
+}
+
+static void
+output_sorted(struct svec *svec, struct ds *output)
+{
+ const char *name;
+ size_t i;
+
+ svec_sort(svec);
+ SVEC_FOR_EACH (i, name, svec) {
+ ds_put_format(output, "%s\n", name);
+ }
+}
+
+static void
+cmd_list_br(struct vsctl_context *ctx)
+{
+ struct shash_node *node;
+ struct vsctl_info info;
+ struct svec bridges;
+
+ get_info(ctx->ovs, &info);
+
+ svec_init(&bridges);
+ SHASH_FOR_EACH (node, &info.bridges) {
+ struct vsctl_bridge *br = node->data;
+ svec_add(&bridges, br->name);
+ }
+ output_sorted(&bridges, &ctx->output);
+ svec_destroy(&bridges);
+
+ free_info(&info);
+}
+
+static void
+cmd_br_exists(struct vsctl_context *ctx)
+{
+ struct vsctl_info info;
+
+ get_info(ctx->ovs, &info);
+ if (!find_bridge(&info, ctx->argv[1], false)) {
+ vsctl_exit(2);
+ }
+ free_info(&info);
+}
+
+/* Returns true if 'b_prefix' (of length 'b_prefix_len') concatenated with 'b'
+ * equals 'a', false otherwise. */
+static bool
+key_matches(const char *a,
+ const char *b_prefix, size_t b_prefix_len, const char *b)
+{
+ return !strncmp(a, b_prefix, b_prefix_len) && !strcmp(a + b_prefix_len, b);
+}
+
+static void
+set_external_id(char **old_keys, char **old_values, size_t old_n,
+ char *key, char *value,
+ char ***new_keysp, char ***new_valuesp, size_t *new_np)
+{
+ char **new_keys;
+ char **new_values;
+ size_t new_n;
+ size_t i;
+
+ new_keys = xmalloc(sizeof *new_keys * (old_n + 1));
+ new_values = xmalloc(sizeof *new_values * (old_n + 1));
+ new_n = 0;
+ for (i = 0; i < old_n; i++) {
+ if (strcmp(key, old_keys[i])) {
+ new_keys[new_n] = old_keys[i];
+ new_values[new_n] = old_values[i];
+ new_n++;
+ }
+ }
+ if (value) {
+ new_keys[new_n] = key;
+ new_values[new_n] = value;
+ new_n++;
+ }
+ *new_keysp = new_keys;
+ *new_valuesp = new_values;
+ *new_np = new_n;
+}
+
+static void
+cmd_br_set_external_id(struct vsctl_context *ctx)
+{
+ struct vsctl_info info;
+ struct vsctl_bridge *bridge;
+ char **keys, **values;
+ size_t n;
+
+ get_info(ctx->ovs, &info);
+ bridge = find_bridge(&info, ctx->argv[1], true);
+ if (bridge->br_cfg) {
+ set_external_id(bridge->br_cfg->key_external_ids,
+ bridge->br_cfg->value_external_ids,
+ bridge->br_cfg->n_external_ids,
+ ctx->argv[2], ctx->argc >= 4 ? ctx->argv[3] : NULL,
+ &keys, &values, &n);
+ ovsrec_bridge_set_external_ids(bridge->br_cfg, keys, values, n);
+ } else {
+ char *key = xasprintf("fake-bridge-%s", ctx->argv[2]);
+ struct vsctl_port *port = shash_find_data(&info.ports, ctx->argv[1]);
+ set_external_id(port->port_cfg->key_external_ids,
+ port->port_cfg->value_external_ids,
+ port->port_cfg->n_external_ids,
+ key, ctx->argc >= 4 ? ctx->argv[3] : NULL,
+ &keys, &values, &n);
+ ovsrec_port_set_external_ids(port->port_cfg, keys, values, n);
+ free(key);
+ }
+ free(keys);
+ free(values);
+
+ free_info(&info);
+}
+
+static void
+get_external_id(char **keys, char **values, size_t n,
+ const char *prefix, const char *key,
+ struct ds *output)
+{
+ size_t prefix_len = strlen(prefix);
+ struct svec svec;
+ size_t i;
+
+ svec_init(&svec);
+ for (i = 0; i < n; i++) {
+ if (!key && !strncmp(keys[i], prefix, prefix_len)) {
+ svec_add_nocopy(&svec, xasprintf("%s=%s",
+ keys[i] + prefix_len, values[i]));
+ } else if (key_matches(keys[i], prefix, prefix_len, key)) {
+ svec_add(&svec, values[i]);
+ break;
+ }
+ }
+ output_sorted(&svec, output);
+ svec_destroy(&svec);
+}
+
+static void
+cmd_br_get_external_id(struct vsctl_context *ctx)
+{
+ struct vsctl_info info;
+ struct vsctl_bridge *bridge;
+
+ get_info(ctx->ovs, &info);
+ bridge = find_bridge(&info, ctx->argv[1], true);
+ if (bridge->br_cfg) {
+ get_external_id(bridge->br_cfg->key_external_ids,
+ bridge->br_cfg->value_external_ids,
+ bridge->br_cfg->n_external_ids,
+ "", ctx->argc >= 3 ? ctx->argv[2] : NULL,
+ &ctx->output);
+ } else {
+ struct vsctl_port *port = shash_find_data(&info.ports, ctx->argv[1]);
+ get_external_id(port->port_cfg->key_external_ids,
+ port->port_cfg->value_external_ids,
+ port->port_cfg->n_external_ids,
+ "fake-bridge-", ctx->argc >= 3 ? ctx->argv[2] : NULL, &ctx->output);
+ }
+ free_info(&info);
+}
+
+
+static void
+cmd_list_ports(struct vsctl_context *ctx)
+{
+ struct vsctl_bridge *br;
+ struct shash_node *node;
+ struct vsctl_info info;
+ struct svec ports;
+
+ get_info(ctx->ovs, &info);
+ br = find_bridge(&info, ctx->argv[1], true);
+
+ svec_init(&ports);
+ SHASH_FOR_EACH (node, &info.ports) {
+ struct vsctl_port *port = node->data;
+
+ if (strcmp(port->port_cfg->name, br->name) && br == port->bridge) {
+ svec_add(&ports, port->port_cfg->name);
+ }
+ }
+ output_sorted(&ports, &ctx->output);
+ svec_destroy(&ports);
+
+ free_info(&info);
+}
+
+static void
+add_port(struct vsctl_context *ctx,
+ const char *br_name, const char *port_name,
+ bool may_exist, bool fake_iface,
+ char *iface_names[], int n_ifaces)
+{
+ struct vsctl_info info;
+ struct vsctl_bridge *bridge;
+ struct ovsrec_interface **ifaces;
+ struct ovsrec_port *port;
+ size_t i;
+
+ get_info(ctx->ovs, &info);
+ if (may_exist) {
+ struct vsctl_port *port;
+
+ port = find_port(&info, port_name, false);
+ if (port) {
+ struct svec want_names, have_names;
+ size_t i;
+
+ svec_init(&want_names);
+ for (i = 0; i < n_ifaces; i++) {
+ svec_add(&want_names, iface_names[i]);
+ }
+ svec_sort(&want_names);
+
+ svec_init(&have_names);
+ for (i = 0; i < port->port_cfg->n_interfaces; i++) {
+ svec_add(&have_names, port->port_cfg->interfaces[i]->name);
+ }
+ svec_sort(&have_names);
+
+ if (strcmp(port->bridge->name, br_name)) {
+ char *command = vsctl_context_to_string(ctx);
+ vsctl_fatal("\"%s\" but %s is actually attached to bridge %s",
+ command, port_name, port->bridge->name);
+ }
+
+ if (!svec_equal(&want_names, &have_names)) {
+ char *have_names_string = svec_join(&have_names, ", ", "");
+ char *command = vsctl_context_to_string(ctx);
+
+ vsctl_fatal("\"%s\" but %s actually has interface(s) %s",
+ command, port_name, have_names_string);
+ }
+
+ svec_destroy(&want_names);
+ svec_destroy(&have_names);
+
+ return;
+ }
+ }
+ check_conflicts(&info, port_name,
+ xasprintf("cannot create a port named %s", port_name));
+ for (i = 0; i < n_ifaces; i++) {
+ check_conflicts(&info, iface_names[i],
+ xasprintf("cannot create an interface named %s",
+ iface_names[i]));
+ }
+ bridge = find_bridge(&info, br_name, true);
+
+ ifaces = xmalloc(n_ifaces * sizeof *ifaces);
+ for (i = 0; i < n_ifaces; i++) {
+ ifaces[i] = ovsrec_interface_insert(ctx->txn);
+ ovsrec_interface_set_name(ifaces[i], iface_names[i]);
+ }
+
+ port = ovsrec_port_insert(ctx->txn);
+ ovsrec_port_set_name(port, port_name);
+ ovsrec_port_set_interfaces(port, ifaces, n_ifaces);
+ ovsrec_port_set_bond_fake_iface(port, fake_iface);
+ free(ifaces);
+
+ if (bridge->vlan) {
+ int64_t tag = bridge->vlan;
+ ovsrec_port_set_tag(port, &tag, 1);
+ }
+
+ bridge_insert_port((bridge->parent ? bridge->parent->br_cfg
+ : bridge->br_cfg), port);
+
+ free_info(&info);
+}
+
+static void
+cmd_add_port(struct vsctl_context *ctx)
+{
+ bool may_exist = shash_find(&ctx->options, "--may-exist") != 0;
+
+ add_port(ctx, ctx->argv[1], ctx->argv[2], may_exist, false,
+ &ctx->argv[2], 1);
+}
+
+static void
+cmd_add_bond(struct vsctl_context *ctx)
+{
+ bool may_exist = shash_find(&ctx->options, "--may-exist") != 0;
+ bool fake_iface = shash_find(&ctx->options, "--fake-iface");
+
+ add_port(ctx, ctx->argv[1], ctx->argv[2], may_exist, fake_iface,
+ &ctx->argv[3], ctx->argc - 3);
+}
+
+static void
+cmd_del_port(struct vsctl_context *ctx)
+{
+ bool must_exist = !shash_find(&ctx->options, "--if-exists");
+ struct vsctl_info info;
+
+ get_info(ctx->ovs, &info);
+ if (ctx->argc == 2) {
+ struct vsctl_port *port = find_port(&info, ctx->argv[1], must_exist);
+ if (port) {
+ del_port(&info, port);
+ }
+ } else if (ctx->argc == 3) {
+ struct vsctl_bridge *bridge = find_bridge(&info, ctx->argv[1], true);
+ struct vsctl_port *port = find_port(&info, ctx->argv[2], must_exist);
+
+ if (port) {
+ if (port->bridge == bridge) {
+ del_port(&info, port);
+ } else if (port->bridge->parent == bridge) {
+ vsctl_fatal("bridge %s does not have a port %s (although its "
+ "parent bridge %s does)",
+ ctx->argv[1], ctx->argv[2], bridge->parent->name);
+ } else {
+ vsctl_fatal("bridge %s does not have a port %s",
+ ctx->argv[1], ctx->argv[2]);
+ }
+ }
+ }
+ free_info(&info);
+}
+
+static void
+cmd_port_to_br(struct vsctl_context *ctx)
+{
+ struct vsctl_port *port;
+ struct vsctl_info info;
+
+ get_info(ctx->ovs, &info);
+ port = find_port(&info, ctx->argv[1], true);
+ ds_put_format(&ctx->output, "%s\n", port->bridge->name);
+ free_info(&info);
+}
+
+static void
+cmd_br_to_vlan(struct vsctl_context *ctx)
+{
+ struct vsctl_bridge *bridge;
+ struct vsctl_info info;
+
+ get_info(ctx->ovs, &info);
+ bridge = find_bridge(&info, ctx->argv[1], true);
+ ds_put_format(&ctx->output, "%d\n", bridge->vlan);
+ free_info(&info);
+}
+
+static void
+cmd_br_to_parent(struct vsctl_context *ctx)
+{
+ struct vsctl_bridge *bridge;
+ struct vsctl_info info;
+
+ get_info(ctx->ovs, &info);
+ bridge = find_bridge(&info, ctx->argv[1], true);
+ if (bridge->parent) {
+ bridge = bridge->parent;
+ }
+ ds_put_format(&ctx->output, "%s\n", bridge->name);
+ free_info(&info);
+}
+
+static void
+cmd_list_ifaces(struct vsctl_context *ctx)
+{
+ struct vsctl_bridge *br;
+ struct shash_node *node;
+ struct vsctl_info info;
+ struct svec ifaces;
+
+ get_info(ctx->ovs, &info);
+ br = find_bridge(&info, ctx->argv[1], true);
+
+ svec_init(&ifaces);
+ SHASH_FOR_EACH (node, &info.ifaces) {
+ struct vsctl_iface *iface = node->data;
+
+ if (strcmp(iface->iface_cfg->name, br->name)
+ && br == iface->port->bridge) {
+ svec_add(&ifaces, iface->iface_cfg->name);
+ }
+ }
+ output_sorted(&ifaces, &ctx->output);
+ svec_destroy(&ifaces);
+
+ free_info(&info);
+}
+
+static void
+cmd_iface_to_br(struct vsctl_context *ctx)
+{
+ struct vsctl_iface *iface;
+ struct vsctl_info info;
+
+ get_info(ctx->ovs, &info);
+ iface = find_iface(&info, ctx->argv[1], true);
+ ds_put_format(&ctx->output, "%s\n", iface->port->bridge->name);
+ free_info(&info);
+}
+
+static void
+cmd_get_controller(struct vsctl_context *ctx)
+{
+ struct vsctl_info info;
+
+ get_info(ctx->ovs, &info);
+
+ if (ctx->argc == 1) {
+ /* Return the controller from the "Open_vSwitch" table */
+ if (info.ctrl) {
+ ds_put_format(&ctx->output, "%s\n", info.ctrl->target);
+ }
+ } else {
+ /* Return the controller for a particular bridge. */
+ struct vsctl_bridge *br = find_bridge(&info, ctx->argv[1], true);
+
+ /* If no controller is explicitly defined for the requested
+ * bridge, fallback to the "Open_vSwitch" table's controller. */
+ if (br->ctrl) {
+ ds_put_format(&ctx->output, "%s\n", br->ctrl->target);
+ } else if (info.ctrl) {
+ ds_put_format(&ctx->output, "%s\n", info.ctrl->target);
+ }
+ }
+
+ free_info(&info);
+}
+
+static void
+cmd_del_controller(struct vsctl_context *ctx)
+{
+ struct vsctl_info info;
+
+ get_info(ctx->ovs, &info);
+
+ if (ctx->argc == 1) {
+ if (info.ctrl) {
+ ovsrec_controller_delete(info.ctrl);
+ ovsrec_open_vswitch_set_controller(ctx->ovs, NULL);
+ }
+ } else {
+ struct vsctl_bridge *br = find_real_bridge(&info, ctx->argv[1], true);
+
+ if (br->ctrl) {
+ ovsrec_controller_delete(br->ctrl);
+ ovsrec_bridge_set_controller(br->br_cfg, NULL);
+ }
+ }
+
+ free_info(&info);
+}
+
+static void
+cmd_set_controller(struct vsctl_context *ctx)
+{
+ struct vsctl_info info;
+ struct ovsrec_controller *ctrl;
+
+ get_info(ctx->ovs, &info);
+
+ if (ctx->argc == 2) {
+ /* Set the controller in the "Open_vSwitch" table. */
+ if (info.ctrl) {
+ ovsrec_controller_delete(info.ctrl);
+ }
+ ctrl = ovsrec_controller_insert(ctx->txn);
+ ovsrec_controller_set_target(ctrl, ctx->argv[1]);
+ ovsrec_open_vswitch_set_controller(ctx->ovs, ctrl);
+ } else {
+ /* Set the controller for a particular bridge. */
+ struct vsctl_bridge *br = find_real_bridge(&info, ctx->argv[1], true);
+
+ if (br->ctrl) {
+ ovsrec_controller_delete(br->ctrl);
+ }
+ ctrl = ovsrec_controller_insert(ctx->txn);
+ ovsrec_controller_set_target(ctrl, ctx->argv[2]);
+ ovsrec_bridge_set_controller(br->br_cfg, ctrl);
+ }
+
+ free_info(&info);
+}
+
+static void
+cmd_get_fail_mode(struct vsctl_context *ctx)
+{
+ struct vsctl_info info;
+ const char *fail_mode = NULL;
+
+ get_info(ctx->ovs, &info);
+
+ if (ctx->argc == 1) {
+ /* Return the fail-mode from the "Open_vSwitch" table */
+ if (info.ctrl && info.ctrl->fail_mode) {
+ fail_mode = info.ctrl->fail_mode;
+ }
+ } else {
+ /* Return the fail-mode for a particular bridge. */
+ struct vsctl_bridge *br = find_bridge(&info, ctx->argv[1], true);
+
+ /* If no controller or fail-mode is explicitly defined for the
+ * requested bridge, fallback to the "Open_vSwitch" table's
+ * setting. */
+ if (br->ctrl && br->ctrl->fail_mode) {
+ fail_mode = br->ctrl->fail_mode;
+ } else if (info.ctrl && info.ctrl->fail_mode) {
+ fail_mode = info.ctrl->fail_mode;
+ }
+ }
+
+ if (fail_mode && strlen(fail_mode)) {
+ ds_put_format(&ctx->output, "%s\n", fail_mode);
+ }
+
+ free_info(&info);
+}
+
+static void
+cmd_del_fail_mode(struct vsctl_context *ctx)
+{
+ struct vsctl_info info;
+
+ get_info(ctx->ovs, &info);
+
+ if (ctx->argc == 1) {
+ if (info.ctrl && info.ctrl->fail_mode) {
+ ovsrec_controller_set_fail_mode(info.ctrl, NULL);
+ }
+ } else {
+ struct vsctl_bridge *br = find_real_bridge(&info, ctx->argv[1], true);
+
+ if (br->ctrl && br->ctrl->fail_mode) {
+ ovsrec_controller_set_fail_mode(br->ctrl, NULL);
+ }
+ }
+
+ free_info(&info);
+}
+
+static void
+cmd_set_fail_mode(struct vsctl_context *ctx)
+{
+ struct vsctl_info info;
+ const char *fail_mode;
+
+ get_info(ctx->ovs, &info);
+
+ fail_mode = (ctx->argc == 2) ? ctx->argv[1] : ctx->argv[2];
+
+ if (strcmp(fail_mode, "standalone") && strcmp(fail_mode, "secure")) {
+ vsctl_fatal("fail-mode must be \"standalone\" or \"secure\"");
+ }
+
+ if (ctx->argc == 2) {
+ /* Set the fail-mode in the "Open_vSwitch" table. */
+ if (!info.ctrl) {
+ vsctl_fatal("no controller declared");
+ }
+ ovsrec_controller_set_fail_mode(info.ctrl, fail_mode);
+ } else {
+ struct vsctl_bridge *br = find_real_bridge(&info, ctx->argv[1], true);
+
+ if (!br->ctrl) {
+ vsctl_fatal("no controller declared for %s", br->name);
+ }
+ ovsrec_controller_set_fail_mode(br->ctrl, fail_mode);
+ }
+
+ free_info(&info);
+}
+
+static void
+cmd_get_ssl(struct vsctl_context *ctx)
+{
+ struct ovsrec_ssl *ssl = ctx->ovs->ssl;
+
+ if (ssl) {
+ ds_put_format(&ctx->output, "Private key: %s\n", ssl->private_key);
+ ds_put_format(&ctx->output, "Certificate: %s\n", ssl->certificate);
+ ds_put_format(&ctx->output, "CA Certificate: %s\n", ssl->ca_cert);
+ ds_put_format(&ctx->output, "Bootstrap: %s\n",
+ ssl->bootstrap_ca_cert ? "true" : "false");
+ }
+}
+
+static void
+cmd_del_ssl(struct vsctl_context *ctx)
+{
+ struct ovsrec_ssl *ssl = ctx->ovs->ssl;
+
+ if (ssl) {
+ ovsrec_ssl_delete(ssl);
+ ovsrec_open_vswitch_set_ssl(ctx->ovs, NULL);
+ }
+}
+
+static void
+cmd_set_ssl(struct vsctl_context *ctx)
+{
+ bool bootstrap = shash_find(&ctx->options, "--bootstrap");
+ struct ovsrec_ssl *ssl = ctx->ovs->ssl;
+
+ if (ssl) {
+ ovsrec_ssl_delete(ssl);
+ }
+ ssl = ovsrec_ssl_insert(ctx->txn);
+
+ ovsrec_ssl_set_private_key(ssl, ctx->argv[1]);
+ ovsrec_ssl_set_certificate(ssl, ctx->argv[2]);
+ ovsrec_ssl_set_ca_cert(ssl, ctx->argv[3]);
+
+ ovsrec_ssl_set_bootstrap_ca_cert(ssl, bootstrap);
+
+ ovsrec_open_vswitch_set_ssl(ctx->ovs, ssl);
+}
+\f
+/* Parameter commands. */
+
+struct vsctl_row_id {
+ const struct ovsdb_idl_table_class *table;
+ const struct ovsdb_idl_column *name_column;
+ const struct ovsdb_idl_column *uuid_column;
+};
+
+struct vsctl_table_class {
+ struct ovsdb_idl_table_class *class;
+ struct vsctl_row_id row_ids[2];
+};
+
+static const struct vsctl_table_class tables[] = {
+ {&ovsrec_table_bridge,
+ {{&ovsrec_table_bridge, &ovsrec_bridge_col_name, NULL},
+ {NULL, NULL, NULL}}},
+
+ {&ovsrec_table_controller,
+ {{&ovsrec_table_bridge,
+ &ovsrec_bridge_col_name,
+ &ovsrec_bridge_col_controller},
+ {&ovsrec_table_open_vswitch,
+ NULL,
+ &ovsrec_open_vswitch_col_controller}}},
+
+ {&ovsrec_table_interface,
+ {{&ovsrec_table_interface, &ovsrec_interface_col_name, NULL},
+ {NULL, NULL, NULL}}},
+
+ {&ovsrec_table_mirror,
+ {{&ovsrec_table_mirror, &ovsrec_mirror_col_name, NULL},
+ {NULL, NULL, NULL}}},
+
+ {&ovsrec_table_netflow,
+ {{&ovsrec_table_bridge,
+ &ovsrec_bridge_col_name,
+ &ovsrec_bridge_col_netflow},
+ {NULL, NULL, NULL}}},
+
+ {&ovsrec_table_open_vswitch,
+ {{&ovsrec_table_open_vswitch, NULL, NULL},
+ {NULL, NULL, NULL}}},
+
+ {&ovsrec_table_port,
+ {{&ovsrec_table_port, &ovsrec_port_col_name, NULL},
+ {NULL, NULL, NULL}}},
+
+ {&ovsrec_table_ssl,
+ {{&ovsrec_table_open_vswitch, NULL, &ovsrec_open_vswitch_col_ssl}}},
+
+ {NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}}
+};
+
+static void
+die_if_error(char *error)
+{
+ if (error) {
+ vsctl_fatal("%s", error);
+ }
+}
+
+static int
+to_lower_and_underscores(unsigned c)
+{
+ return c == '-' ? '_' : tolower(c);
+}
+
+static unsigned int
+score_partial_match(const char *name, const char *s)
+{
+ int score;
+
+ if (!strcmp(name, s)) {
+ return UINT_MAX;
+ }
+ for (score = 0; ; score++, name++, s++) {
+ if (to_lower_and_underscores(*name) != to_lower_and_underscores(*s)) {
+ break;
+ } else if (*name == '\0') {
+ return UINT_MAX - 1;
+ }
+ }
+ return *s == '\0' ? score : 0;
+}
+
+static const struct vsctl_table_class *
+get_table(const char *table_name)
+{
+ const struct vsctl_table_class *table;
+ const struct vsctl_table_class *best_match = NULL;
+ unsigned int best_score = 0;
+
+ for (table = tables; table->class; table++) {
+ unsigned int score = score_partial_match(table->class->name,
+ table_name);
+ if (score > best_score) {
+ best_match = table;
+ best_score = score;
+ } else if (score == best_score) {
+ best_match = NULL;
+ }
+ }
+ if (best_match) {
+ return best_match;
+ } else if (best_score) {
+ vsctl_fatal("multiple table names match \"%s\"", table_name);
+ } else {
+ vsctl_fatal("unknown table \"%s\"", table_name);
+ }
+}
+
+static const struct ovsdb_idl_row *
+get_row_by_id(struct vsctl_context *ctx, const struct vsctl_table_class *table,
+ const struct vsctl_row_id *id, const char *record_id)
+{
+ const struct ovsdb_idl_row *referrer, *final;
+
+ if (!id->table) {
+ return NULL;
+ }
+
+ if (!id->name_column) {
+ if (strcmp(record_id, ".")) {
+ return NULL;
+ }
+ referrer = ovsdb_idl_first_row(ctx->idl, id->table);
+ if (!referrer || ovsdb_idl_next_row(referrer)) {
+ return NULL;
+ }
+ } else {
+ const struct ovsdb_idl_row *row;
+ unsigned int best_score = 0;
+
+ /* It might make sense to relax this assertion. */
+ assert(id->name_column->type.key.type == OVSDB_TYPE_STRING);
+
+ referrer = NULL;
+ for (row = ovsdb_idl_first_row(ctx->idl, id->table);
+ row != NULL && best_score != UINT_MAX;
+ row = ovsdb_idl_next_row(row))
+ {
+ struct ovsdb_datum name;
+
+ ovsdb_idl_txn_read(row, id->name_column, &name);
+ if (name.n == 1) {
+ unsigned int score = score_partial_match(name.keys[0].string,
+ record_id);
+ if (score > best_score) {
+ referrer = row;
+ best_score = score;
+ } else if (score == best_score) {
+ referrer = NULL;
+ }
+ }
+ ovsdb_datum_destroy(&name, &id->name_column->type);
+ }
+ if (best_score && !referrer) {
+ vsctl_fatal("multiple rows in %s match \"%s\"",
+ table->class->name, record_id);
+ }
+ }
+ if (!referrer) {
+ return NULL;
+ }
+
+ final = NULL;
+ if (id->uuid_column) {
+ struct ovsdb_datum uuid;
+
+ assert(id->uuid_column->type.key.type == OVSDB_TYPE_UUID);
+ assert(id->uuid_column->type.value.type == OVSDB_TYPE_VOID);
+
+ ovsdb_idl_txn_read(referrer, id->uuid_column, &uuid);
+ if (uuid.n == 1) {
+ final = ovsdb_idl_get_row_for_uuid(ctx->idl, table->class,
+ &uuid.keys[0].uuid);
+ }
+ ovsdb_datum_destroy(&uuid, &id->uuid_column->type);
+ } else {
+ final = referrer;
+ }
+
+ return final;
+}
+
+static const struct ovsdb_idl_row *
+get_row(struct vsctl_context *ctx,
+ const struct vsctl_table_class *table, const char *record_id)
+{
+ const struct ovsdb_idl_row *row;
+ struct uuid uuid;
+
+ if (uuid_from_string(&uuid, record_id)) {
+ row = ovsdb_idl_get_row_for_uuid(ctx->idl, table->class, &uuid);
+ } else {
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(table->row_ids); i++) {
+ row = get_row_by_id(ctx, table, &table->row_ids[i], record_id);
+ if (row) {
+ break;
+ }
+ }
+ }
+ return row;
+}
+
+static const struct ovsdb_idl_row *
+must_get_row(struct vsctl_context *ctx,
+ const struct vsctl_table_class *table, const char *record_id)
+{
+ const struct ovsdb_idl_row *row = get_row(ctx, table, record_id);
+ if (!row) {
+ vsctl_fatal("no row \"%s\" in table %s",
+ record_id, table->class->name);
+ }
+ return row;
+}
+
+static char *
+get_column(const struct vsctl_table_class *table, const char *column_name,
+ const struct ovsdb_idl_column **columnp)
+{
+ const struct ovsdb_idl_column *best_match = NULL;
+ unsigned int best_score = 0;
+ size_t i;
+
+ for (i = 0; i < table->class->n_columns; i++) {
+ const struct ovsdb_idl_column *column = &table->class->columns[i];
+ unsigned int score = score_partial_match(column->name, column_name);
+ if (score > best_score) {
+ best_match = column;
+ best_score = score;
+ } else if (score == best_score) {
+ best_match = NULL;
+ }
+ }
+
+ *columnp = best_match;
+ if (best_match) {
+ return NULL;
+ } else if (best_score) {
+ return xasprintf("%s contains more than one column whose name "
+ "matches \"%s\"", table->class->name, column_name);
+ } else {
+ return xasprintf("%s does not contain a column whose name matches "
+ "\"%s\"", table->class->name, column_name);
+ }
+}
+
+static char * WARN_UNUSED_RESULT
+parse_column_key_value(const char *arg, const struct vsctl_table_class *table,
+ const struct ovsdb_idl_column **columnp,
+ char **keyp, char **valuep)
+{
+ const char *p = arg;
+ char *error;
+
+ assert(columnp || keyp);
+ if (keyp) {
+ *keyp = NULL;
+ }
+ if (valuep) {
+ *valuep = NULL;
+ }
+
+ /* Parse column name. */
+ if (columnp) {
+ char *column_name;
+
+ error = ovsdb_token_parse(&p, &column_name);
+ if (error) {
+ goto error;
+ }
+ if (column_name[0] == '\0') {
+ free(column_name);
+ error = xasprintf("%s: missing column name", arg);
+ goto error;
+ }
+ error = get_column(table, column_name, columnp);
+ free(column_name);
+ if (error) {
+ goto error;
+ }
+ }
+
+ /* Parse key string. */
+ if (*p == ':' || !columnp) {
+ if (columnp) {
+ p++;
+ } else if (!keyp) {
+ error = xasprintf("%s: key not accepted here", arg);
+ goto error;
+ }
+ error = ovsdb_token_parse(&p, keyp);
+ if (error) {
+ goto error;
+ }
+ } else if (keyp) {
+ *keyp = NULL;
+ }
+
+ /* Parse value string. */
+ if (*p == '=') {
+ if (!valuep) {
+ error = xasprintf("%s: value not accepted here", arg);
+ goto error;
+ }
+ *valuep = xstrdup(p + 1);
+ } else {
+ if (valuep) {
+ *valuep = NULL;
+ }
+ if (*p != '\0') {
+ error = xasprintf("%s: trailing garbage \"%s\" in argument",
+ arg, p);
+ goto error;
+ }
+ }
+ return NULL;
+
+error:
+ if (columnp) {
+ *columnp = NULL;
+ }
+ if (keyp) {
+ free(*keyp);
+ *keyp = NULL;
+ }
+ if (valuep) {
+ free(*valuep);
+ *valuep = NULL;
+ }
+ return error;
+}
+
+static void
+cmd_get(struct vsctl_context *ctx)
+{
+ bool if_exists = shash_find(&ctx->options, "--if-exists");
+ const char *table_name = ctx->argv[1];
+ const char *record_id = ctx->argv[2];
+ const struct vsctl_table_class *table;
+ const struct ovsdb_idl_row *row;
+ struct ds *out = &ctx->output;
+ int i;
+
+ table = get_table(table_name);
+ row = must_get_row(ctx, table, record_id);
+ for (i = 3; i < ctx->argc; i++) {
+ const struct ovsdb_idl_column *column;
+ struct ovsdb_datum datum;
+ char *key_string;
+
+ die_if_error(parse_column_key_value(ctx->argv[i], table,
+ &column, &key_string, NULL));
+
+ ovsdb_idl_txn_read(row, column, &datum);
+ if (key_string) {
+ union ovsdb_atom key;
+ unsigned int idx;
+
+ if (column->type.value.type == OVSDB_TYPE_VOID) {
+ vsctl_fatal("cannot specify key to get for non-map column %s",
+ column->name);
+ }
+
+ die_if_error(ovsdb_atom_from_string(&key,
+ &column->type.key,
+ key_string));
+
+ idx = ovsdb_datum_find_key(&datum, &key,
+ column->type.key.type);
+ if (idx == UINT_MAX) {
+ if (!if_exists) {
+ vsctl_fatal("no key \"%s\" in %s record \"%s\" column %s",
+ key_string, table->class->name, record_id,
+ column->name);
+ }
+ } else {
+ ovsdb_atom_to_string(&datum.values[idx],
+ column->type.value.type, out);
+ }
+ ovsdb_atom_destroy(&key, column->type.key.type);
+ } else {
+ ovsdb_datum_to_string(&datum, &column->type, out);
+ }
+ ds_put_char(out, '\n');
+ ovsdb_datum_destroy(&datum, &column->type);
+
+ free(key_string);
+ }
+}
+
+static void
+list_record(const struct vsctl_table_class *table,
+ const struct ovsdb_idl_row *row, struct ds *out)
+{
+ size_t i;
+
+ ds_put_format(out, "%-20s: "UUID_FMT"\n", "_uuid",
+ UUID_ARGS(&row->uuid));
+ for (i = 0; i < table->class->n_columns; i++) {
+ const struct ovsdb_idl_column *column = &table->class->columns[i];
+ struct ovsdb_datum datum;
+
+ ovsdb_idl_txn_read(row, column, &datum);
+
+ ds_put_format(out, "%-20s: ", column->name);
+ ovsdb_datum_to_string(&datum, &column->type, out);
+ ds_put_char(out, '\n');
+
+ ovsdb_datum_destroy(&datum, &column->type);
+ }
+}
+
+static void
+cmd_list(struct vsctl_context *ctx)
+{
+ const char *table_name = ctx->argv[1];
+ const struct vsctl_table_class *table;
+ struct ds *out = &ctx->output;
+ int i;
+
+ table = get_table(table_name);
+ if (ctx->argc > 2) {
+ for (i = 2; i < ctx->argc; i++) {
+ if (i > 2) {
+ ds_put_char(out, '\n');
+ }
+ list_record(table, must_get_row(ctx, table, ctx->argv[i]), out);
+ }
+ } else {
+ const struct ovsdb_idl_row *row;
+ bool first;
+
+ for (row = ovsdb_idl_first_row(ctx->idl, table->class), first = true;
+ row != NULL;
+ row = ovsdb_idl_next_row(row), first = false) {
+ if (!first) {
+ ds_put_char(out, '\n');
+ }
+ list_record(table, row, out);
+ }
+ }
+}
+
+static void
+set_column(const struct vsctl_table_class *table,
+ const struct ovsdb_idl_row *row, const char *arg)
+{
+ const struct ovsdb_idl_column *column;
+ char *key_string, *value_string;
+ char *error;
+
+ error = parse_column_key_value(arg, table, &column, &key_string,
+ &value_string);
+ die_if_error(error);
+ if (!value_string) {
+ vsctl_fatal("%s: missing value", arg);
+ }
+
+ if (key_string) {
+ union ovsdb_atom key, value;
+ struct ovsdb_datum old, new;
+
+ if (column->type.value.type == OVSDB_TYPE_VOID) {
+ vsctl_fatal("cannot specify key to set for non-map column %s",
+ column->name);
+ }
+
+ die_if_error(ovsdb_atom_from_string(&key, &column->type.key,
+ key_string));
+ die_if_error(ovsdb_atom_from_string(&value, &column->type.value,
+ value_string));
+
+ ovsdb_datum_init_empty(&new);
+ ovsdb_datum_add_unsafe(&new, &key, &value, &column->type);
+
+ ovsdb_atom_destroy(&key, column->type.key.type);
+ ovsdb_atom_destroy(&value, column->type.value.type);
+
+ ovsdb_idl_txn_read(row, column, &old);
+ ovsdb_datum_union(&old, &new, &column->type, true);
+ ovsdb_idl_txn_write(row, column, &old);
+
+ ovsdb_datum_destroy(&new, &column->type);
+ } else {
+ struct ovsdb_datum datum;
+
+ die_if_error(ovsdb_datum_from_string(&datum, &column->type,
+ value_string));
+ ovsdb_idl_txn_write(row, column, &datum);
+ }
+
+ free(key_string);
+ free(value_string);
+}
+
+static void
+cmd_set(struct vsctl_context *ctx)
+{
+ const char *table_name = ctx->argv[1];
+ const char *record_id = ctx->argv[2];
+ const struct vsctl_table_class *table;
+ const struct ovsdb_idl_row *row;
+ int i;
+
+ table = get_table(table_name);
+ row = must_get_row(ctx, table, record_id);
+ for (i = 3; i < ctx->argc; i++) {
+ set_column(table, row, ctx->argv[i]);
+ }
+}
+
+static void
+cmd_add(struct vsctl_context *ctx)
+{
+ const char *table_name = ctx->argv[1];
+ const char *record_id = ctx->argv[2];
+ const char *column_name = ctx->argv[3];
+ const struct vsctl_table_class *table;
+ const struct ovsdb_idl_column *column;
+ const struct ovsdb_idl_row *row;
+ const struct ovsdb_type *type;
+ struct ovsdb_datum old;
+ int i;
+
+ table = get_table(table_name);
+ row = must_get_row(ctx, table, record_id);
+ die_if_error(get_column(table, column_name, &column));
+
+ type = &column->type;
+ ovsdb_idl_txn_read(row, column, &old);
+ for (i = 4; i < ctx->argc; i++) {
+ struct ovsdb_type add_type;
+ struct ovsdb_datum add;
+
+ add_type = *type;
+ add_type.n_min = 1;
+ add_type.n_max = UINT_MAX;
+ die_if_error(ovsdb_datum_from_string(&add, &add_type, ctx->argv[i]));
+ ovsdb_datum_union(&old, &add, type, false);
+ ovsdb_datum_destroy(&add, type);
+ }
+ if (old.n > type->n_max) {
+ vsctl_fatal("\"add\" operation would put %u %s in column %s of "
+ "table %s but the maximum number is %u",
+ old.n,
+ type->value.type == OVSDB_TYPE_VOID ? "values" : "pairs",
+ column->name, table->class->name, type->n_max);
+ }
+ ovsdb_idl_txn_write(row, column, &old);
+}
+
+static void
+cmd_remove(struct vsctl_context *ctx)
+{
+ const char *table_name = ctx->argv[1];
+ const char *record_id = ctx->argv[2];
+ const char *column_name = ctx->argv[3];
+ const struct vsctl_table_class *table;
+ const struct ovsdb_idl_column *column;
+ const struct ovsdb_idl_row *row;
+ const struct ovsdb_type *type;
+ struct ovsdb_datum old;
+ int i;
+
+ table = get_table(table_name);
+ row = must_get_row(ctx, table, record_id);
+ die_if_error(get_column(table, column_name, &column));
+
+ type = &column->type;
+ ovsdb_idl_txn_read(row, column, &old);
+ for (i = 4; i < ctx->argc; i++) {
+ struct ovsdb_type rm_type;
+ struct ovsdb_datum rm;
+ char *error;
+
+ rm_type = *type;
+ rm_type.n_min = 1;
+ rm_type.n_max = UINT_MAX;
+ error = ovsdb_datum_from_string(&rm, &rm_type, ctx->argv[i]);
+ if (error && ovsdb_type_is_map(&rm_type)) {
+ free(error);
+ rm_type.value.type = OVSDB_TYPE_VOID;
+ die_if_error(ovsdb_datum_from_string(&rm, &rm_type, ctx->argv[i]));
+ }
+ ovsdb_datum_subtract(&old, type, &rm, &rm_type);
+ ovsdb_datum_destroy(&rm, &rm_type);
+ }
+ if (old.n < type->n_min) {
+ vsctl_fatal("\"remove\" operation would put %u %s in column %s of "
+ "table %s but the minimun number is %u",
+ old.n,
+ type->value.type == OVSDB_TYPE_VOID ? "values" : "pairs",
+ column->name, table->class->name, type->n_min);
+ }
+ ovsdb_idl_txn_write(row, column, &old);
+}
+
+static void
+cmd_clear(struct vsctl_context *ctx)
+{
+ const char *table_name = ctx->argv[1];
+ const char *record_id = ctx->argv[2];
+ const struct vsctl_table_class *table;
+ const struct ovsdb_idl_row *row;
+ int i;
+
+ table = get_table(table_name);
+ row = must_get_row(ctx, table, record_id);
+ for (i = 3; i < ctx->argc; i++) {
+ const struct ovsdb_idl_column *column;
+ const struct ovsdb_type *type;
+ struct ovsdb_datum datum;
+
+ die_if_error(get_column(table, ctx->argv[i], &column));
+
+ type = &column->type;
+ if (type->n_min > 0) {
+ vsctl_fatal("\"clear\" operation cannot be applied to column %s "
+ "of table %s, which is not allowed to be empty",
+ column->name, table->class->name);
+ }
+
+ ovsdb_datum_init_empty(&datum);
+ ovsdb_idl_txn_write(row, column, &datum);
+ }
+}
+
+static void
+cmd_create(struct vsctl_context *ctx)
+{
+ const char *table_name = ctx->argv[1];
+ const struct vsctl_table_class *table;
+ const struct ovsdb_idl_row *row;
+ int i;
+
+ table = get_table(table_name);
+ row = ovsdb_idl_txn_insert(ctx->txn, table->class);
+ for (i = 2; i < ctx->argc; i++) {
+ set_column(table, row, ctx->argv[i]);
+ }
+ ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(&row->uuid));
+}
+
+/* This function may be used as the 'postprocess' function for commands that
+ * insert new rows into the database. It expects that the command's 'run'
+ * function prints the UUID reported by ovsdb_idl_txn_insert() as the command's
+ * sole output. It replaces that output by the row's permanent UUID assigned
+ * by the database server and appends a new-line.
+ *
+ * Currently we use this only for "create", because the higher-level commands
+ * are supposed to be independent of the actual structure of the vswitch
+ * configuration. */
+static void
+post_create(struct vsctl_context *ctx)
+{
+ const struct uuid *real;
+ struct uuid dummy;
+
+ uuid_from_string(&dummy, ds_cstr(&ctx->output));
+ real = ovsdb_idl_txn_get_insert_uuid(ctx->txn, &dummy);
+ if (real) {
+ ds_clear(&ctx->output);
+ ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(real));
+ }
+ ds_put_char(&ctx->output, '\n');
+}
+
+static void
+cmd_destroy(struct vsctl_context *ctx)
+{
+ bool must_exist = !shash_find(&ctx->options, "--if-exists");
+ const char *table_name = ctx->argv[1];
+ const struct vsctl_table_class *table;
+ int i;
+
+ table = get_table(table_name);
+ for (i = 2; i < ctx->argc; i++) {
+ const struct ovsdb_idl_row *row;
+
+ row = (must_exist ? must_get_row : get_row)(ctx, table, ctx->argv[i]);
+ if (row) {
+ ovsdb_idl_txn_delete(row);
+ }
+ }
+}
+\f
+static struct json *
+where_uuid_equals(const struct uuid *uuid)
+{
+ return
+ json_array_create_1(
+ json_array_create_3(
+ json_string_create("_uuid"),
+ json_string_create("=="),
+ json_array_create_2(
+ json_string_create("uuid"),
+ json_string_create_nocopy(
+ xasprintf(UUID_FMT, UUID_ARGS(uuid))))));
+}
+
+static void
+vsctl_context_init(struct vsctl_context *ctx, struct vsctl_command *command,
+ struct ovsdb_idl *idl, struct ovsdb_idl_txn *txn,
+ const struct ovsrec_open_vswitch *ovs)
+{
+ ctx->argc = command->argc;
+ ctx->argv = command->argv;
+ ctx->options = command->options;
+
+ ds_swap(&ctx->output, &command->output);
+ ctx->idl = idl;
+ ctx->txn = txn;
+ ctx->ovs = ovs;
+
+}
+
+static void
+vsctl_context_done(struct vsctl_context *ctx, struct vsctl_command *command)
+{
+ ds_swap(&ctx->output, &command->output);
+}
+
+static void
+do_vsctl(const char *args, struct vsctl_command *commands, size_t n_commands,
+ struct ovsdb_idl *idl)
+{
+ struct ovsdb_idl_txn *txn;
+ const struct ovsrec_open_vswitch *ovs;
+ enum ovsdb_idl_txn_status status;
+ struct vsctl_command *c;
+ int64_t next_cfg = 0;
+ char *comment;
+ char *error;
+
+ txn = the_idl_txn = ovsdb_idl_txn_create(idl);
+ if (dry_run) {
+ ovsdb_idl_txn_set_dry_run(txn);
+ }
+
+ comment = xasprintf("ovs-vsctl: %s", args);
+ ovsdb_idl_txn_add_comment(txn, comment);
+ free(comment);
+
+ ovs = ovsrec_open_vswitch_first(idl);
+ if (!ovs) {
+ /* XXX add verification that table is empty */
+ ovs = ovsrec_open_vswitch_insert(txn);
+ }
+
+ if (wait_for_reload) {
+ struct json *where = where_uuid_equals(&ovs->header_.uuid);
+ ovsdb_idl_txn_increment(txn, "Open_vSwitch", "next_cfg", where);
+ json_destroy(where);
+ }
+
+ for (c = commands; c < &commands[n_commands]; c++) {
+ struct vsctl_context ctx;
+
+ ds_init(&c->output);
+ vsctl_context_init(&ctx, c, idl, txn, ovs);
+ (c->syntax->run)(&ctx);
+ vsctl_context_done(&ctx, c);
+ }
+
+ while ((status = ovsdb_idl_txn_commit(txn)) == TXN_INCOMPLETE) {
+ ovsdb_idl_run(idl);
+ ovsdb_idl_wait(idl);
+ ovsdb_idl_txn_wait(txn);
+ poll_block();
+ }
+ if (wait_for_reload && status == TXN_SUCCESS) {
+ next_cfg = ovsdb_idl_txn_get_increment_new_value(txn);
+ }
+ for (c = commands; c < &commands[n_commands]; c++) {
+ if (c->syntax->postprocess) {
+ struct vsctl_context ctx;
+
+ vsctl_context_init(&ctx, c, idl, txn, ovs);
+ (c->syntax->postprocess)(&ctx);
+ vsctl_context_done(&ctx, c);
+ }
+ }
+ error = xstrdup(ovsdb_idl_txn_get_error(txn));
+ ovsdb_idl_txn_destroy(txn);
+ the_idl_txn = NULL;
+
+ switch (status) {
+ case TXN_INCOMPLETE:
+ NOT_REACHED();
+
+ case TXN_ABORTED:
+ /* Should not happen--we never call ovsdb_idl_txn_abort(). */
+ vsctl_fatal("transaction aborted");
+
+ case TXN_UNCHANGED:
+ case TXN_SUCCESS:
+ break;
+
+ case TXN_TRY_AGAIN:
+ for (c = commands; c < &commands[n_commands]; c++) {
+ ds_destroy(&c->output);
+ }
+ free(error);
+ return;
+
+ case TXN_ERROR:
+ vsctl_fatal("transaction error: %s", error);
+
+ default:
+ NOT_REACHED();
+ }
+ free(error);
+
+ for (c = commands; c < &commands[n_commands]; c++) {
+ struct ds *ds = &c->output;
+ if (oneline) {
+ size_t j;
+
+ ds_chomp(ds, '\n');
+ for (j = 0; j < ds->length; j++) {
+ int c = ds->string[j];
+ switch (c) {
+ case '\n':
+ fputs("\\n", stdout);
+ break;
+
+ case '\\':
+ fputs("\\\\", stdout);
+ break;
+
+ default:
+ putchar(c);
+ }
+ }
+ putchar('\n');
+ } else {
+ fputs(ds_cstr(ds), stdout);
+ }
+ ds_destroy(&c->output);
+ shash_destroy(&c->options);
+ }
+ free(commands);
+
+ if (wait_for_reload && status != TXN_UNCHANGED) {
+ for (;;) {
+ const struct ovsrec_open_vswitch *ovs;
+
+ ovsdb_idl_run(idl);
+ OVSREC_OPEN_VSWITCH_FOR_EACH (ovs, idl) {
+ if (ovs->cur_cfg >= next_cfg) {
+ goto done;
+ }
+ }
+ ovsdb_idl_wait(idl);
+ poll_block();
+ }
+ done: ;
+ }
+ ovsdb_idl_destroy(idl);
+
+ exit(EXIT_SUCCESS);
+}
+
+static const struct vsctl_command_syntax all_commands[] = {
+ /* Open vSwitch commands. */
+ {"init", 0, 0, cmd_init, NULL, ""},
+
+ /* Bridge commands. */
+ {"add-br", 1, 3, cmd_add_br, NULL, "--may-exist"},
+ {"del-br", 1, 1, cmd_del_br, NULL, "--if-exists"},
+ {"list-br", 0, 0, cmd_list_br, NULL, ""},
+ {"br-exists", 1, 1, cmd_br_exists, NULL, ""},
+ {"br-to-vlan", 1, 1, cmd_br_to_vlan, NULL, ""},
+ {"br-to-parent", 1, 1, cmd_br_to_parent, NULL, ""},
+ {"br-set-external-id", 2, 3, cmd_br_set_external_id, NULL, ""},
+ {"br-get-external-id", 1, 2, cmd_br_get_external_id, NULL, ""},
+
+ /* Port commands. */
+ {"list-ports", 1, 1, cmd_list_ports, NULL, ""},
+ {"add-port", 2, 2, cmd_add_port, NULL, "--may-exist"},
+ {"add-bond", 4, INT_MAX, cmd_add_bond, NULL, "--may-exist,--fake-iface"},
+ {"del-port", 1, 2, cmd_del_port, NULL, "--if-exists"},
+ {"port-to-br", 1, 1, cmd_port_to_br, NULL, ""},
+
+ /* Interface commands. */
+ {"list-ifaces", 1, 1, cmd_list_ifaces, NULL, ""},
+ {"iface-to-br", 1, 1, cmd_iface_to_br, NULL, ""},
+
+ /* Controller commands. */
+ {"get-controller", 0, 1, cmd_get_controller, NULL, ""},
+ {"del-controller", 0, 1, cmd_del_controller, NULL, ""},
+ {"set-controller", 1, 2, cmd_set_controller, NULL, ""},
+ {"get-fail-mode", 0, 1, cmd_get_fail_mode, NULL, ""},
+ {"del-fail-mode", 0, 1, cmd_del_fail_mode, NULL, ""},
+ {"set-fail-mode", 1, 2, cmd_set_fail_mode, NULL, ""},
+
+ /* SSL commands. */
+ {"get-ssl", 0, 0, cmd_get_ssl, NULL, ""},
+ {"del-ssl", 0, 0, cmd_del_ssl, NULL, ""},
+ {"set-ssl", 3, 3, cmd_set_ssl, NULL, "--bootstrap"},
+
+ /* Parameter commands. */
+ {"get", 3, INT_MAX, cmd_get, NULL, "--if-exists"},
+ {"list", 1, INT_MAX, cmd_list, NULL, ""},
+ {"set", 3, INT_MAX, cmd_set, NULL, ""},
+ {"add", 4, INT_MAX, cmd_add, NULL, ""},
+ {"remove", 4, INT_MAX, cmd_remove, NULL, ""},
+ {"clear", 3, INT_MAX, cmd_clear, NULL, ""},
+ {"create", 2, INT_MAX, cmd_create, post_create, ""},
+ {"destroy", 1, INT_MAX, cmd_destroy, NULL, "--if-exists"},
+
+ {NULL, 0, 0, NULL, NULL, NULL},
+};
+
}
static int
-create_iface(const char *iface_name)
-{
- return set_up_iface(iface_name, true);
-}
-
-static int
-reconfigure_iface(const char *iface_name)
+reconfigure_iface(const struct ovsrec_interface *iface_cfg, struct iface *iface)
{
- return set_up_iface(iface_name, false);
+ return set_up_iface(iface_cfg, iface, false);
}
-static void
-destroy_iface(const char *iface_name)
-{
- netdev_destroy(iface_name);
-}
-
-
-/* iterate_and_prune_ifaces() callback function that opens the network device
- * for 'iface', if it is not already open, and retrieves the interface's MAC
- * address and carrier status. */
static bool
- check_iface_netdev(struct bridge *br UNUSED, struct iface *iface,
- void *aux UNUSED)
-init_iface_netdev(struct bridge *br OVS_UNUSED, struct iface *iface,
- void *aux OVS_UNUSED)
++check_iface_netdev(struct bridge *br OVS_UNUSED, struct iface *iface,
++ void *aux OVS_UNUSED)
{
- if (iface->netdev) {
- return true;
- } else if (!netdev_open(iface->name, NETDEV_ETH_TYPE_NONE,
- &iface->netdev)) {
- netdev_get_carrier(iface->netdev, &iface->enabled);
- return true;
- } else {
- /* If the network device can't be opened, then we're not going to try
- * to do anything with this interface. */
- return false;
+ if (!iface->netdev) {
+ int error = set_up_iface(iface->cfg, iface, true);
+ if (error) {
+ VLOG_WARN("could not open netdev on %s, dropping: %s", iface->name,
+ strerror(error));
+ return false;
+ }
}
+
+ return true;
}
static bool
}
static bool
- set_iface_properties(struct bridge *br UNUSED, struct iface *iface,
- void *aux UNUSED)
+ set_iface_properties(struct bridge *br OVS_UNUSED, struct iface *iface,
+ void *aux OVS_UNUSED)
{
- int rate, burst;
-
/* Set policing attributes. */
- rate = cfg_get_int(0, "port.%s.ingress.policing-rate", iface->name);
- burst = cfg_get_int(0, "port.%s.ingress.policing-burst", iface->name);
- netdev_set_policing(iface->netdev, rate, burst);
+ netdev_set_policing(iface->netdev,
+ iface->cfg->ingress_policing_rate,
+ iface->cfg->ingress_policing_burst);
/* Set MAC address of internal interfaces other than the local
* interface. */
\f
/* Bridge unixctl user interface functions. */
static void
-bridge_unixctl_fdb_show(struct unixctl_conn *conn, const char *args)
+bridge_unixctl_fdb_show(struct unixctl_conn *conn,
- const char *args, void *aux UNUSED)
++ const char *args, void *aux OVS_UNUSED)
{
struct ds ds = DS_EMPTY_INITIALIZER;
const struct bridge *br;
/* Handle requests for a listing of all flows known by the OpenFlow
* stack, including those normally hidden. */
static void
-bridge_unixctl_dump_flows(struct unixctl_conn *conn, const char *args)
+bridge_unixctl_dump_flows(struct unixctl_conn *conn,
- const char *args, void *aux UNUSED)
++ const char *args, void *aux OVS_UNUSED)
{
struct bridge *br;
struct ds results;
/* Bonding unixctl user interface functions. */
static void
-bond_unixctl_list(struct unixctl_conn *conn, const char *args OVS_UNUSED)
+bond_unixctl_list(struct unixctl_conn *conn,
- const char *args UNUSED, void *aux UNUSED)
++ const char *args OVS_UNUSED, void *aux OVS_UNUSED)
{
struct ds ds = DS_EMPTY_INITIALIZER;
const struct bridge *br;
}
static void
-bond_unixctl_show(struct unixctl_conn *conn, const char *args)
+bond_unixctl_show(struct unixctl_conn *conn,
- const char *args, void *aux UNUSED)
++ const char *args, void *aux OVS_UNUSED)
{
struct ds ds = DS_EMPTY_INITIALIZER;
const struct port *port;
}
static void
-bond_unixctl_migrate(struct unixctl_conn *conn, const char *args_)
+bond_unixctl_migrate(struct unixctl_conn *conn, const char *args_,
- void *aux UNUSED)
++ void *aux OVS_UNUSED)
{
char *args = (char *) args_;
char *save_ptr = NULL;
}
static void
-bond_unixctl_set_active_slave(struct unixctl_conn *conn, const char *args_)
+bond_unixctl_set_active_slave(struct unixctl_conn *conn, const char *args_,
- void *aux UNUSED)
++ void *aux OVS_UNUSED)
{
char *args = (char *) args_;
char *save_ptr = NULL;
}
static void
-bond_unixctl_enable_slave(struct unixctl_conn *conn, const char *args)
+bond_unixctl_enable_slave(struct unixctl_conn *conn, const char *args,
- void *aux UNUSED)
++ void *aux OVS_UNUSED)
{
enable_slave(conn, args, true);
}
static void
-bond_unixctl_disable_slave(struct unixctl_conn *conn, const char *args)
+bond_unixctl_disable_slave(struct unixctl_conn *conn, const char *args,
- void *aux UNUSED)
++ void *aux OVS_UNUSED)
{
enable_slave(conn, args, false);
}
static void
-bond_unixctl_hash(struct unixctl_conn *conn, const char *args)
+bond_unixctl_hash(struct unixctl_conn *conn, const char *args,
- void *aux UNUSED)
++ void *aux OVS_UNUSED)
{
uint8_t mac[ETH_ADDR_LEN];
uint8_t hash;
# log("Network PIF %s not currently attached (%s)" % (rec['uuid'],pifrec['uuid']))
# continue
nwrec = db().get_network_record(rec['network'])
- cfgmod_argv += ['--add=bridge.%s.xs-network-uuids=%s' % (bridge, nwrec['uuid'])]
-
- cfgmod_argv += ["# deconfigure ipdev %s" % ipdev]
- cfgmod_argv += datapath_deconfigure_ipdev(ipdev)
- cfgmod_argv += ["# reconfigure ipdev %s" % ipdev]
- cfgmod_argv += ['--add=bridge.%s.port=%s' % (bridge, ipdev)]
- if bridge == ipdev:
- cfgmod_argv += ['--add=bridge.%s.mac=%s' % (bridge, dprec['MAC'])]
- else:
- cfgmod_argv += ['--add=iface.%s.mac=%s' % (ipdev, dprec['MAC'])]
-
- if pif_is_vlan(self._pif):
- cfgmod_argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
- cfgmod_argv += ['--add=iface.%s.internal=true' % (ipdev)]
- cfgmod_argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
+ xs_network_uuids += [nwrec['uuid']]
+
+ vsctl_argv += ['# configure xs-network-uuids']
+ vsctl_argv += ['--', 'br-set-external-id', bridge,
+ 'xs-network-uuids', ';'.join(xs_network_uuids)]
+
+ if ipdev != bridge:
+ vsctl_argv += ["# deconfigure ipdev %s" % ipdev]
+ vsctl_argv += datapath_deconfigure_ipdev(ipdev)
+ vsctl_argv += ["# reconfigure ipdev %s" % ipdev]
+ vsctl_argv += ['--', 'add-port', bridge, ipdev]
+
+ # XXX Needs support in ovs-vsctl
+ #if bridge == ipdev:
- # vsctl_argv += ['--add=bridge.%s.mac=%s' % (bridge, pifrec['MAC'])]
++ # vsctl_argv += ['--add=bridge.%s.mac=%s' % (bridge, dprec['MAC'])]
+ #else:
- # vsctl_argv += ['--add=iface.%s.mac=%s' % (ipdev, pifrec['MAC'])]
++ # vsctl_argv += ['--add=iface.%s.mac=%s' % (ipdev, dprec['MAC'])]
- self._cfgmod_argv = cfgmod_argv
+ self._vsctl_argv = vsctl_argv
self._extra_ports = extra_ports
def bring_down_existing(self):