From owner-svn-soc-all@FreeBSD.ORG Sun May 31 21:27:29 2015 Return-Path: Delivered-To: svn-soc-all@FreeBSD.org Received: from mx1.freebsd.org (mx1.freebsd.org [8.8.178.115]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id 35784A71 for ; Sun, 31 May 2015 21:27:29 +0000 (UTC) (envelope-from roam@FreeBSD.org) Received: from socsvn.freebsd.org (socsvn.freebsd.org [IPv6:2001:1900:2254:206a::50:2]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id 227811A33 for ; Sun, 31 May 2015 21:27:29 +0000 (UTC) (envelope-from roam@FreeBSD.org) Received: from socsvn.freebsd.org ([127.0.1.124]) by socsvn.freebsd.org (8.14.9/8.14.9) with ESMTP id t4VLRTdu035811 for ; Sun, 31 May 2015 21:27:29 GMT (envelope-from roam@FreeBSD.org) Received: (from www@localhost) by socsvn.freebsd.org (8.14.9/8.14.9/Submit) id t4VLRR7M035786 for svn-soc-all@FreeBSD.org; Sun, 31 May 2015 21:27:27 GMT (envelope-from roam@FreeBSD.org) Date: Sun, 31 May 2015 21:27:27 GMT Message-Id: <201505312127.t4VLRR7M035786@socsvn.freebsd.org> X-Authentication-Warning: socsvn.freebsd.org: www set sender to roam@FreeBSD.org using -f From: roam@FreeBSD.org To: svn-soc-all@FreeBSD.org Subject: socsvn commit: r286476 - soc2015/roam/ng_ayiya MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-soc-all@freebsd.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: SVN commit messages for the entire Summer of Code repository List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 31 May 2015 21:27:29 -0000 Author: roam Date: Sun May 31 21:27:26 2015 New Revision: 286476 URL: http://svnweb.FreeBSD.org/socsvn/?view=rev&rev=286476 Log: Start a Netgraph AYIYA module. Not doing much just yet, busy being created. ObQuote: "A beginning is a very delicate time." Added: soc2015/roam/ng_ayiya/ soc2015/roam/ng_ayiya/Makefile soc2015/roam/ng_ayiya/ng_ayiya.c soc2015/roam/ng_ayiya/ng_ayiya.h soc2015/roam/ng_ayiya/scaffold.pl (contents, props changed) Added: soc2015/roam/ng_ayiya/Makefile ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ soc2015/roam/ng_ayiya/Makefile Sun May 31 21:27:26 2015 (r286476) @@ -0,0 +1,45 @@ +#!/usr/bin/make +# +# Copyright (c) 2015 Peter Pentchev +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +KMOD= ng_ayiya +SRCS= ng_ayiya.c + +.include + +SUDO?= sudo +PERL?= perl +SCAFFOLD_PL?= scaffold.pl +SCAFFOLD_V?= -v +SCAFFOLD?= ${SUDO} ${PERL} ${SCAFFOLD_PL} ${SCAFFOLD_V} + +up: + ${SCAFFOLD} setup + +down: + ${SCAFFOLD} shutdown + +down-all: + ${SCAFFOLD} shutdown all Added: soc2015/roam/ng_ayiya/ng_ayiya.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ soc2015/roam/ng_ayiya/ng_ayiya.c Sun May 31 21:27:26 2015 (r286476) @@ -0,0 +1,261 @@ +/** + * Copyright (c) 2015 Peter Pentchev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Netgraph AYIYA node. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ng_ayiya.h" + +#ifdef NG_SEPARATE_MALLOC +static MALLOC_DEFINE(M_NETGRAPH_AYIYA, "netgraph_ayiya", "netgraph AYIYA node"); +#else +#define M_NETGRAPH_AYIYA M_NETGRAPH +#endif + +static ng_constructor_t ng_ayiya_constructor; +static ng_rcvmsg_t ng_ayiya_rcvmsg; +static ng_newhook_t ng_ayiya_newhook; +static ng_disconnect_t ng_ayiya_disconnect; +static ng_shutdown_t ng_ayiya_shutdown; + +static struct ng_type typestruct = { + .version = NG_ABI_VERSION, + .name = NG_AYIYA_NODE_TYPE, + + .constructor = ng_ayiya_constructor, + .rcvmsg = ng_ayiya_rcvmsg, + .newhook = ng_ayiya_newhook, + .disconnect = ng_ayiya_disconnect, + .shutdown = ng_ayiya_shutdown, +}; +NETGRAPH_INIT(ayiya, &typestruct); + +enum { + AYIYA_HOOK_CONTROL, + AYIYA_HOOK_INET6, + AYIYA_HOOK_AYIYA, + AYIYA_HOOK_LAST +}; + +#define NG_AYIYA_DECLARE_HOOK(i, n) { .idx = i, .name = n, .len = sizeof(n) - 1 } +static const struct { + int idx; + const char *name; + size_t len; +} hookdefs[AYIYA_HOOK_LAST] = { + NG_AYIYA_DECLARE_HOOK(AYIYA_HOOK_CONTROL, "control"), + NG_AYIYA_DECLARE_HOOK(AYIYA_HOOK_INET6, "inet6"), + NG_AYIYA_DECLARE_HOOK(AYIYA_HOOK_AYIYA, "ayiya"), +}; + +struct ng_ayiya_private { + node_p node; + char * secrethash; + hook_p hooks[AYIYA_HOOK_LAST]; +}; +typedef struct ng_ayiya_private *priv_p; + +static int +ng_ayiya_constructor(const node_p node) +{ + priv_p priv; + + priv = malloc(sizeof(*priv), M_NETGRAPH_AYIYA, M_WAITOK | M_ZERO); + NG_NODE_SET_PRIVATE(node, priv); + priv->node = node; + return (0); +} + +#define ERROUT(x) do { error = (x); goto done; } while (0) + +static void +build_status(struct sbuf * const sb, const node_p node, const bool json) +{ + priv_p priv = NG_NODE_PRIVATE(node); + if (json) + sbuf_printf(sb, + "{\n" + "\t\"id\":\t\"%x\",\n" + "\t\"name\":\t\"%s\",\n" + "\t\"has_secret\":\t%s,\n" + "\t\"hooks\": {\n", + node->nd_ID, node->nd_name, + priv->secrethash? "true": "false"); + else + sbuf_printf(sb, + "Node [%x] %s\n" + "Secret hash %sset\n", + node->nd_ID, node->nd_name, + priv->secrethash? "": "not "); + + for (int idx = 0; idx < AYIYA_HOOK_LAST; idx++) { + const char * const hname = hookdefs[idx].name; + const size_t hidx = hookdefs[idx].idx; + const hook_p hook = priv->hooks[hidx]; + + if (hook != NULL) { + const node_p peer = NG_PEER_NODE(hook); + if (json) + sbuf_printf(sb, + "\t\t\"%s\": {\n" + "\t\t\t\"hook_name\":\t\"%s\",\n" + "\t\t\t\"id\":\t\"%x\",\n" + "\t\t\t\"name\":\t\"%s\",\n" + "\t\t\t\"type\":\t\"%s\"\n" + "\t\t}%s\n", + hname, NG_HOOK_NAME(hook), + peer->nd_ID, peer->nd_name, + peer->nd_type->name, + idx < AYIYA_HOOK_LAST - 1? ",": ""); + else + sbuf_printf(sb, + "Hook '%s' (%s) connected to node " + "[%x] '%s' of type '%s'.\n", + NG_HOOK_NAME(hook), hname, + peer->nd_ID, peer->nd_name, + peer->nd_type->name); + } else { + if (json) + sbuf_printf(sb, "\t\t\"%s\":\tnull%s\n", + hname, + idx < AYIYA_HOOK_LAST - 1? ",": ""); + else + sbuf_printf(sb, + "Hook '%s' not connected yet.\n", + hname); + } + } + + if (json) + sbuf_printf(sb, "\t}\n}"); +} + +static int +ng_ayiya_rcvmsg(const node_p node, item_p item, const hook_p lasthook) +{ + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + switch (msg->header.typecookie) { + case NGM_GENERIC_COOKIE: + switch (msg->header.cmd) { + case NGM_TEXT_CONFIG: + case NGM_TEXT_STATUS: + if (msg->header.arglen != 0) + ERROUT(EINVAL); + + struct sbuf *sb = sbuf_new_auto(); + build_status(sb, node, + msg->header.cmd == NGM_TEXT_CONFIG); + if (sbuf_finish(sb) != 0) { + sbuf_delete(sb); + ERROUT(ENOMEM); + } + const ssize_t len = sbuf_len(sb); + NG_MKRESPONSE(resp, msg, len + 1, M_WAITOK); + bcopy(sbuf_data(sb), resp->data, len); + resp->data[len] = '\0'; + sbuf_delete(sb); + break; + + default: + error = EINVAL; + break; + } + break; + + default: + error = EINVAL; + break; + } + +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return (error); +} + +static int +ng_ayiya_newhook(const node_p node, const hook_p hook, + const char * const name) +{ + int idx; + for (idx = 0; idx < AYIYA_HOOK_LAST; idx++) { + const char * const hname = hookdefs[idx].name; + const size_t hlen = hookdefs[idx].len; + + if (strncmp(name, hname, hlen) == 0 && + (name[hlen] == '\0' || name[hlen] == '/')) + { + idx = hookdefs[idx].idx; + break; + } + } + if (idx == AYIYA_HOOK_LAST) + return (EINVAL); + const priv_p priv = NG_NODE_PRIVATE(node); + if (priv->hooks[idx] != NULL) + return (EISCONN); + + /* TODO: some more checks here */ + + priv->hooks[idx] = hook; + return (0); +} + +static int +ng_ayiya_disconnect(const hook_p hook) +{ + priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + int idx; + for (idx = 0; idx < AYIYA_HOOK_LAST; idx++) + if (priv->hooks[idx] == hook) + break; + if (idx == AYIYA_HOOK_LAST) + panic("%s", __func__); + priv->hooks[idx] = NULL; + return (0); +} + +static int +ng_ayiya_shutdown(const node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + free(priv, M_NETGRAPH_AYIYA); + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(node); + return (0); +} Added: soc2015/roam/ng_ayiya/ng_ayiya.h ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ soc2015/roam/ng_ayiya/ng_ayiya.h Sun May 31 21:27:26 2015 (r286476) @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2015 Peter Pentchev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _NETGRAPH_NG_AYIYA_H +#define _NETGRAPH_NG_AYIYA_H + +/* Node type name and magic cookie */ +#define NG_AYIYA_NODE_TYPE "ayiya" +#define NGM_AYIYA_COOKIE 1432823247 + +#endif Added: soc2015/roam/ng_ayiya/scaffold.pl ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ soc2015/roam/ng_ayiya/scaffold.pl Sun May 31 21:27:26 2015 (r286476) @@ -0,0 +1,358 @@ +#!/usr/bin/perl +# +# Copyright (c) 2015 Peter Pentchev +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +use v5.12; +use strict; +use warnings; + +use Getopt::Std; +use JSON::PP; +use POSIX qw/:sys_wait_h/; + +my $debug = 0; +my ($tempfile, $tempname); + +sub debug($); +sub usage($); +sub version(); + +sub get_ayiya; +sub ngctl_list(); +sub ngctl($ @); +sub run_command(@); +sub check_wait_result($ $ $); + +sub cmd_help($ @); +sub cmd_setup($ @); +sub cmd_shutdown($ @); +sub cmd_status($ @); +sub cmd_version($ @); + +my %cmds = ( + build => \&cmd_setup, + erect => \&cmd_setup, + help => \&cmd_help, + setup => \&cmd_setup, + shutdown => \&cmd_shutdown, + teardown => \&cmd_shutdown, + status => \&cmd_status, + version => \&cmd_version, +); + +MAIN: +{ + my %opts; + + getopts('hVv', \%opts) or usage(1); + version() if $opts{V}; + usage 0 if $opts{h}; + exit 0 if $opts{V} || $opts{h}; + $debug = $opts{v}; + + usage 1 unless @ARGV; + my $cmd = shift @ARGV; + my $handler = $cmds{$cmd}; + if (!defined $handler) { + say STDERR "Invalid command '$cmd'"; + usage 1; + } else { + $handler->($cmd, @ARGV); + } +} + +sub usage($) +{ + my ($err) = @_; + my $s = <{all}}; + if (!@all) { + say 'No AYIYA nodes configured'; + return; + } + say scalar(@all).' AYIYA node'.(@all == 1? '': 's').' found'. + (@all? ': '.(join ', ', map "[$_->{id}] $_->{name}", @all): ''); + my $node = $ay->{ours}; + if (!defined $node) { + say "Ours is not there"; + return; + } + say "Ours is there: [$node->{id}] $node->{name}"; + # TODO: config, status, hooks, interfaces +} + +sub cmd_shutdown($ @) +{ + my ($cmd, @args) = @_; + + my $all; + if (@args == 1 && $args[0] eq 'all') { + $all = 1; + } elsif (@args) { + say STDERR "Invalid arguments to 'shutdown'"; + usage 1; + } + + my $ay = get_ayiya; + if ($ay->{ours}) { + debug "Shutting down our node $ay->{ours}->{id}"; + ngctl 'shutdown', "[$ay->{ours}->{id}]:"; + } else { + say "Our node not found"; + } + if ($all) { + debug "Shutting down ".scalar(@{$ay->{others}}). + " other node(s)"; + for (@{$ay->{others}}) { + debug "- $_->{id}"; + ngctl 'shutdown', "[$_->{id}]:"; + } + } + + if ((run_command 'kldstat') =~ /ng_ayiya\.ko/) { + run_command 'make', 'unload'; + } else { + debug "The ng_ayiya.ko module is not even loaded"; + } +} + +sub cmd_setup($ @) +{ + my ($cmd, @args) = @_; + + if (@args) { + say STDERR "The 'status' command does not need any arguments"; + usage 1; + } + + if ((run_command 'kldstat') !~ /ng_ayiya\.ko/) { + debug "Trying to build the ng_ayiya.ko module"; + debug run_command 'make', 'depend'; + debug run_command 'make'; + debug "Trying to load the ng_ayiya.ko module"; + run_command 'make', 'load'; + } else { + debug "The ng_ayiya.ko module seems to be loaded already"; + } + + my $ay = get_ayiya; + if (!$ay->{ours}) { + my %found = map { ($_->{id}, 1) } @{$ay->{all}}; + + debug "Creating a new AYIYA node"; + ngctl 'mkpeer', 'ayiya', 'a', 'control/create'; + $ay = get_ayiya; + my @new = map $_->{id}, @{$ay->{all}}; + debug "Looking for an ID in (".join(' ', sort @new). + ") that's not in (".join(' ', sort keys %found).")"; + my $id; + for (@new) { + if (!defined $found{$_}) { + $id = $_; + last; + } + } + if (!defined $id) { + die "Internal error: no new AYIYA nodes\n"; + } + debug "- found $id"; + ngctl 'name', "[$id]:", 'sc_ayiya'; + $ay = get_ayiya; + if (!$ay->{ours} || $ay->{ours}->{id} ne $id) { + die "Internal error: get_ayiya() did not recognize ". + "node [$id] as ours\n"; + } + } else { + debug "Our node already there"; + } + + # TODO: interfaces, hooks... + debug "Setup complete, our node is ". + "[$ay->{ours}->{id}] $ay->{ours}->{name}"; +} + +sub get_ayiya() +{ + my $nodes = ngctl_list; + my $res = { + all => $nodes->{type}{ayiya}, + }; + my $node; + my @ay = @{$res->{all} // []}; + debug "Got ".scalar(@ay)." AYIYA node(s), looking for ours..."; + for(my $i = 0; $i < @ay; $i++) { + if ($ay[$i]->{name} eq 'sc_ayiya') { + debug "- found it at position $i"; + ($node) = splice @ay, $i, 1; + last; + } + } + $res->{ours} = $node; + $res->{others} = \@ay; + + if (defined $node) { + my $js = ngctl 'config', "$node->{name}:"; + $js =~ s/\A[^{]*//s; + debug "- got ".length($js)." characters of JSON"; + my $d = decode_json $js; + if (!defined($d) || ref $d ne 'HASH' || + grep !exists $d->{$_}, qw/id name has_secret hooks/) { + die "Node [$node->{id}] '$node->{name}' returned ". + "an invalid JSON configuration\n"; + } + debug "- got keys: ".join(' ', sort keys %{$d}); + } + return $res; +} + +sub ngctl_list() +{ + my $s = ngctl 'list'; + + my %nodes; + my @lines = split /\n+/, $s; + for (@lines) { + if (/^\s*Name:\s*(\S+)\s+Type:\s*(\S+)\s+ID:\s*0*(\S+)\s+Num hooks:\s*(\S+)\s*$/) { + my ($name, $type, $id, $hooks) = ($1, $2, $3, $4); + my $node = { + name => $name, + type => $type, + id => $id, + num_hooks => $hooks, + }; + $nodes{id}{$id} = $node; + push @{$nodes{type}{$type}}, $node; + if ($name ne '') { + $nodes{name}{$name} = $node; + } + } + } + return \%nodes; +} + +sub ngctl($ @) +{ + my ($cmd, @args) = @_; + + return run_command 'ngctl', $cmd, @args; +} + +sub run_command(@) +{ + my @cmd = @_; + debug "About to run @cmd"; + my $pid = open(my $pipe, '-|'); + if (!defined $pid) { + die "Could not fork for '@cmd': $!\n"; + } elsif ($pid == 0) { + exec { $cmd[0] } @cmd; + die "Could not run '@cmd': $!\n"; + } + + my $output; + { + local $/; + $output = <$pipe>; + } + my $res = close $pipe; + my $msg = $!; + my $status = $?; + check_wait_result $status, $pid, "@cmd"; + if (!$res) { + die "Some error occurred closing the pipe from '@cmd': $msg\n"; + } + return $output; +}