Improve infrastructure for Unix socket-based local management.
authorBen Pfaff <blp@nicira.com>
Thu, 9 Apr 2009 21:32:12 +0000 (14:32 -0700)
committerBen Pfaff <blp@nicira.com>
Thu, 9 Apr 2009 21:32:12 +0000 (14:32 -0700)
"vlog_socket" was essentially a framework for management of a process
over a simple Unix domain socket interface.  Unfortunately it was a little
too simple:

    * It was not extensible for use by clients other than vlog.

    * It was not reliable, since it was based on datagram sockets.

    * It tried to hide itself using poll_fd_callback(), instead of exposing
      itself through the poll loop as does almost every other entity in
      the build tree.

This commit replaces vlog_socket by unixctl, which fixes these problems:

    * Arbitrary commands may now be registered.

    * Use of stream sockets makes it reliable.

    * The interface is exposed to clients.

16 files changed:
controller/controller.c
lib/automake.mk
lib/socket-util.c
lib/socket-util.h
lib/unixctl.c [new file with mode: 0644]
lib/unixctl.h [new file with mode: 0644]
lib/vlog-modules.def
lib/vlog-socket.c [deleted file]
lib/vlog-socket.h [deleted file]
lib/vlog.c
secchan/main.c
utilities/ofp-discover.c
utilities/vlogconf.8.in
utilities/vlogconf.c
vswitchd/brcompatd.c
vswitchd/vswitchd.c

index 41f25478cc31b3897637aa88f164bc7bbb2b2b58..fcebf675d0ae78d01e7175973b401f29e8b3a23e 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+/* Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford
  * Junior University
  * 
  * We are making the OpenFlow specification and associated documentation
 #include "poll-loop.h"
 #include "rconn.h"
 #include "timeval.h"
+#include "unixctl.h"
 #include "util.h"
 #include "vconn-ssl.h"
 #include "vconn.h"
-#include "vlog-socket.h"
 
 #include "vlog.h"
 #define THIS_MODULE VLM_controller
@@ -83,6 +83,7 @@ static void usage(void) NO_RETURN;
 int
 main(int argc, char *argv[])
 {
+    struct unixctl_server *unixctl;
     struct switch_ switches[MAX_SWITCHES];
     struct pvconn *listeners[MAX_LISTENERS];
     int n_switches, n_listeners;
@@ -135,9 +136,9 @@ main(int argc, char *argv[])
     die_if_already_running();
     daemonize();
 
-    retval = vlog_server_listen(NULL, NULL);
+    retval = unixctl_server_create(NULL, &unixctl);
     if (retval) {
-        ofp_fatal(retval, "Could not listen for vlog connections");
+        ofp_fatal(retval, "Could not listen for unixctl connections");
     }
 
     while (n_switches > 0 || n_listeners > 0) {
@@ -188,6 +189,8 @@ main(int argc, char *argv[])
             lswitch_run(this->lswitch, this->rconn);
         }
 
+        unixctl_server_run(unixctl);
+
         /* Wait for something to happen. */
         if (n_switches < MAX_SWITCHES) {
             for (i = 0; i < n_listeners; i++) {
@@ -200,6 +203,7 @@ main(int argc, char *argv[])
             rconn_recv_wait(sw->rconn);
             lswitch_wait(sw->lswitch);
         }
+        unixctl_server_wait(unixctl);
         poll_block();
     }
 
index 68502afc965b70b9871ce2ab9ca8b05c48c9ab57..66edf4938396724e7ea12d01d27a1d39bc604e21 100644 (file)
@@ -86,6 +86,8 @@ lib_libopenflow_a_SOURCES = \
        lib/timeval.c \
        lib/timeval.h \
        lib/type-props.h \
+       lib/unixctl.c \
+       lib/unixctl.h \
        lib/util.c \
        lib/util.h \
        lib/valgrind.h \
@@ -98,8 +100,6 @@ lib_libopenflow_a_SOURCES = \
        lib/vconn.c \
        lib/vconn.h \
        lib/vlog-modules.def \
-       lib/vlog-socket.c \
-       lib/vlog-socket.h \
        lib/vlog.c \
        lib/vlog.h \
        lib/xtoxll.h
index d0ff05668fb9aee3eaf4c241a6669c0cae8b3c94..dc55f1b50bd7444798a4a8168fc8b1ecf2b0f3f3 100644 (file)
@@ -315,3 +315,46 @@ guess_netmask(uint32_t ip)
             : (ip >> 29) == 6 ? htonl(0xffffff00) /* Class C */
             : htonl(0));                          /* ??? */
 }
+
+int
+read_fully(int fd, void *p_, size_t size, size_t *bytes_read)
+{
+    uint8_t *p = p_;
+
+    *bytes_read = 0;
+    while (size > 0) {
+        ssize_t retval = read(fd, p, size);
+        if (retval > 0) {
+            *bytes_read += retval;
+            size -= retval;
+            p += retval;
+        } else if (retval == 0) {
+            return EOF;
+        } else if (errno != EINTR) {
+            return errno;
+        }
+    }
+    return 0;
+}
+
+int
+write_fully(int fd, const void *p_, size_t size, size_t *bytes_written)
+{
+    const uint8_t *p = p_;
+
+    *bytes_written = 0;
+    while (size > 0) {
+        ssize_t retval = write(fd, p, size);
+        if (retval > 0) {
+            *bytes_written += retval;
+            size -= retval;
+            p += retval;
+        } else if (retval == 0) {
+            VLOG_WARN("write returned 0");
+            return EPROTO;
+        } else if (errno != EINTR) {
+            return errno;
+        }
+    }
+    return 0;
+}
index fd38a494c55d083e452e17f3c72e5d80c9c139ec..db6b0baa45c69b26c14b40006ff465413e4d2e35 100644 (file)
@@ -50,4 +50,7 @@ int make_unix_socket(int style, bool nonblock, bool passcred,
 int get_unix_name_len(socklen_t sun_len);
 uint32_t guess_netmask(uint32_t ip);
 
+int read_fully(int fd, void *, size_t, size_t *bytes_read);
+int write_fully(int fd, const void *, size_t, size_t *bytes_written);
+
 #endif /* socket-util.h */
diff --git a/lib/unixctl.c b/lib/unixctl.c
new file mode 100644 (file)
index 0000000..81794f2
--- /dev/null
@@ -0,0 +1,606 @@
+/* Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford
+ * Junior University
+ * 
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * 
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#include <config.h>
+#include "unixctl.h"
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "dirs.h"
+#include "dynamic-string.h"
+#include "fatal-signal.h"
+#include "list.h"
+#include "ofpbuf.h"
+#include "poll-loop.h"
+#include "shash.h"
+#include "socket-util.h"
+#include "util.h"
+
+#ifndef SCM_CREDENTIALS
+#include <time.h>
+#endif
+
+#define THIS_MODULE VLM_unixctl
+#include "vlog.h"
+\f
+struct unixctl_command {
+    void (*cb)(struct unixctl_conn *, const char *args);
+};
+
+struct unixctl_conn {
+    struct list node;
+    int fd;
+
+    enum { S_RECV, S_PROCESS, S_SEND } state;
+    struct ofpbuf in;
+    struct ds out;
+    size_t out_pos;
+};
+
+/* Server for control connection. */
+struct unixctl_server {
+    char *path;
+    int fd;
+    struct list conns;
+};
+
+/* Client for control connection. */
+struct unixctl_client {
+    char *connect_path;
+    char *bind_path;
+    FILE *stream;
+};
+
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
+
+static struct shash commands = SHASH_INITIALIZER(&commands);
+
+static void
+unixctl_help(struct unixctl_conn *conn, const char *args UNUSED)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    struct shash_node *node;
+
+    ds_put_cstr(&ds, "The available commands are:\n");
+    HMAP_FOR_EACH (node, struct shash_node, node, &commands.map) {
+        ds_put_format(&ds, "\t%s\n", node->name);
+    }
+    unixctl_command_reply(conn, 214, ds_cstr(&ds));
+    ds_destroy(&ds);
+}
+
+void
+unixctl_command_register(const char *name,
+                         void (*cb)(struct unixctl_conn *, const char *args))
+{
+    struct unixctl_command *command;
+
+    assert(!shash_find_data(&commands, name)
+           || shash_find_data(&commands, name) == cb);
+    command = xmalloc(sizeof *command);
+    command->cb = cb;
+    shash_add(&commands, name, command);
+}
+
+static const char *
+translate_reply_code(int code)
+{
+    switch (code) {
+    case 200: return "OK";
+    case 201: return "Created";
+    case 202: return "Accepted";
+    case 204: return "No Content";
+    case 211: return "System Status";
+    case 214: return "Help";
+    case 400: return "Bad Request";
+    case 401: return "Unauthorized";
+    case 403: return "Forbidden";
+    case 404: return "Not Found";
+    case 500: return "Internal Server Error";
+    case 501: return "Invalid Argument";
+    case 503: return "Service Unavailable";
+    default: return "Unknown";
+    }
+}
+
+void
+unixctl_command_reply(struct unixctl_conn *conn,
+                      int code, const char *body)
+{
+    struct ds *out = &conn->out;
+
+    assert(conn->state == S_PROCESS);
+    conn->state = S_SEND;
+    conn->out_pos = 0;
+
+    ds_clear(out);
+    ds_put_format(out, "%03d %s\n", code, translate_reply_code(code));
+    if (body) {
+        const char *p;
+        for (p = body; *p != '\0'; ) {
+            size_t n = strcspn(p, "\n");
+
+            if (*p == '.') {
+                ds_put_char(out, '.');
+            }
+            ds_put_buffer(out, p, n);
+            ds_put_char(out, '\n');
+            p += n;
+            if (*p == '\n') {
+                p++;
+            }
+        }
+    }
+    ds_put_cstr(out, ".\n");
+}
+
+/* Creates a unixctl server listening on 'path', which may be:
+ *
+ *      - NULL, in which case <rundir>/<program>.<pid>.ctl is used.
+ *
+ *      - A name that does not start with '/', in which case it is put in
+ *        <rundir>.
+ *
+ *      - An absolute path (starting with '/') that gives the exact name of
+ *        the Unix domain socket to listen on.
+ *
+ * A program that (optionally) daemonizes itself should call this function
+ * *after* daemonization, so that the socket name contains the pid of the
+ * daemon instead of the pid of the program that exited.  (Otherwise, "vlogconf
+ * --target <program>.pid" will fail.)
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  If successful,
+ * sets '*serverp' to the new unixctl_server, otherwise to NULL. */
+int
+unixctl_server_create(const char *path, struct unixctl_server **serverp)
+{
+    struct unixctl_server *server;
+    int error;
+
+    unixctl_command_register("help", unixctl_help);
+
+    server = xmalloc(sizeof *server);
+    list_init(&server->conns);
+
+    if (path) {
+        if (path[0] == '/') {
+            server->path = xstrdup(path);
+        } else {
+            server->path = xasprintf("%s/%s", ofp_rundir, path);
+        }
+    } else {
+        server->path = xasprintf("%s/%s.%ld.ctl", ofp_rundir,
+                                 program_name, (long int) getpid());
+    }
+
+    server->fd = make_unix_socket(SOCK_STREAM, true, false, server->path,
+                                  NULL);
+    if (server->fd < 0) {
+        error = -server->fd;
+        fprintf(stderr, "Could not initialize control socket %s (%s)\n",
+                server->path, strerror(error));
+        goto error;
+    }
+
+    if (chmod(server->path, S_IRUSR | S_IWUSR) < 0) {
+        error = errno;
+        fprintf(stderr, "Failed to chmod control socket %s (%s)\n",
+                server->path, strerror(error));
+        goto error;
+    }
+
+    if (listen(server->fd, 10) < 0) {
+        error = errno;
+        fprintf(stderr, "Failed to listen on control socket %s (%s)\n",
+                server->path, strerror(error));
+        goto error;
+    }
+
+    *serverp = server;
+    return 0;
+
+error:
+    if (server->fd >= 0) {
+        close(server->fd);
+    }
+    free(server->path);
+    free(server);
+    *serverp = NULL;
+    return error;
+}
+
+static void
+new_connection(struct unixctl_server *server, int fd)
+{
+    struct unixctl_conn *conn;
+
+    set_nonblocking(fd);
+
+    conn = xmalloc(sizeof *conn);
+    list_push_back(&server->conns, &conn->node);
+    conn->fd = fd;
+    conn->state = S_RECV;
+    ofpbuf_init(&conn->in, 128);
+    ds_init(&conn->out);
+    conn->out_pos = 0;
+}
+
+static int
+run_connection_output(struct unixctl_conn *conn)
+{
+    while (conn->out_pos < conn->out.length) {
+        size_t bytes_written;
+        int error;
+
+        error = write_fully(conn->fd, conn->out.string + conn->out_pos,
+                            conn->out.length - conn->out_pos, &bytes_written);
+        conn->out_pos += bytes_written;
+        if (error) {
+            return error;
+        }
+    }
+    conn->state = S_RECV;
+    return 0;
+}
+
+static void
+process_command(struct unixctl_conn *conn, char *s)
+{
+    struct unixctl_command *command;
+    size_t name_len;
+    char *name, *args;
+
+    conn->state = S_PROCESS;
+
+    name = s;
+    name_len = strcspn(name, " ");
+    args = name + name_len;
+    args += strspn(args, " ");
+    name[name_len] = '\0';
+
+    command = shash_find_data(&commands, name);
+    if (command) {
+        command->cb(conn, args);
+    } else {
+        char *msg = xasprintf("\"%s\" is not a valid command", name);
+        unixctl_command_reply(conn, 400, msg);
+        free(msg);
+    }
+}
+
+static int
+run_connection_input(struct unixctl_conn *conn)
+{
+    for (;;) {
+        size_t bytes_read;
+        char *newline;
+        int error;
+
+        newline = memchr(conn->in.data, '\n', conn->in.size);
+        if (newline) {
+            char *command = conn->in.data;
+            size_t n = newline - command + 1;
+
+            if (n > 0 && newline[-1] == '\r') {
+                newline--;
+            }
+            *newline = '\0';
+
+            process_command(conn, command);
+
+            ofpbuf_pull(&conn->in, n);
+            if (!conn->in.size) {
+                ofpbuf_clear(&conn->in);
+            }
+            return 0;
+        }
+
+        ofpbuf_prealloc_tailroom(&conn->in, 128);
+        error = read_fully(conn->fd, ofpbuf_tail(&conn->in),
+                           ofpbuf_tailroom(&conn->in), &bytes_read);
+        conn->in.size += bytes_read;
+        if (conn->in.size > 65536) {
+            VLOG_WARN_RL(&rl, "excess command length, killing connection");
+            return EPROTO;
+        }
+        if (error) {
+            if (error == EAGAIN || error == EWOULDBLOCK) {
+                if (!bytes_read) {
+                    return EAGAIN;
+                }
+            } else {
+                if (error != EOF || conn->in.size != 0) {
+                    VLOG_WARN_RL(&rl, "read failed: %s",
+                                 (error == EOF
+                                  ? "connection dropped mid-command"
+                                  : strerror(error)));
+                }
+                return error;
+            }
+        }
+    }
+}
+
+static int
+run_connection(struct unixctl_conn *conn)
+{
+    int old_state;
+    do {
+        int error;
+
+        old_state = conn->state;
+        switch (conn->state) {
+        case S_RECV:
+            error = run_connection_input(conn);
+            break;
+
+        case S_PROCESS:
+            error = 0;
+            break;
+
+        case S_SEND:
+            error = run_connection_output(conn);
+            break;
+
+        default:
+            NOT_REACHED();
+        }
+        if (error) {
+            return error;
+        }
+    } while (conn->state != old_state);
+    return 0;
+}
+
+static void
+kill_connection(struct unixctl_conn *conn)
+{
+    list_remove(&conn->node);
+    ofpbuf_uninit(&conn->in);
+    ds_destroy(&conn->out);
+    close(conn->fd);
+    free(conn);
+}
+
+void
+unixctl_server_run(struct unixctl_server *server)
+{
+    struct unixctl_conn *conn, *next;
+    int i;
+
+    for (i = 0; i < 10; i++) {
+        int fd = accept(server->fd, NULL, NULL);
+        if (fd < 0) {
+            if (errno != EAGAIN && errno != EWOULDBLOCK) {
+                VLOG_WARN_RL(&rl, "accept failed: %s", strerror(errno));
+            }
+            break;
+        }
+        new_connection(server, fd);
+    }
+
+    LIST_FOR_EACH_SAFE (conn, next,
+                        struct unixctl_conn, node, &server->conns) {
+        int error = run_connection(conn);
+        if (error && error != EAGAIN) {
+            kill_connection(conn);
+        }
+    }
+}
+
+void
+unixctl_server_wait(struct unixctl_server *server)
+{
+    struct unixctl_conn *conn;
+
+    poll_fd_wait(server->fd, POLLIN);
+    LIST_FOR_EACH (conn, struct unixctl_conn, node, &server->conns) {
+        if (conn->state == S_RECV) {
+            poll_fd_wait(conn->fd, POLLIN);
+        } else if (conn->state == S_SEND) {
+            poll_fd_wait(conn->fd, POLLOUT);
+        }
+    }
+}
+
+/* Destroys 'server' and stops listening for connections. */
+void
+unixctl_server_destroy(struct unixctl_server *server)
+{
+    if (server) {
+        struct unixctl_conn *conn, *next;
+
+        LIST_FOR_EACH_SAFE (conn, next,
+                            struct unixctl_conn, node, &server->conns) {
+            kill_connection(conn);
+        }
+
+        close(server->fd);
+        unlink(server->path);
+        fatal_signal_remove_file_to_unlink(server->path);
+        free(server->path);
+        free(server);
+    }
+}
+\f
+/* Connects to a Vlog server socket.  'path' should be the name of a Vlog
+ * server socket.  If it does not start with '/', it will be prefixed with
+ * ofp_rundir (e.g. /var/run).
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  If successful,
+ * sets '*clientp' to the new unixctl_client, otherwise to NULL. */
+int
+unixctl_client_create(const char *path, struct unixctl_client **clientp)
+{
+    static int counter;
+    struct unixctl_client *client;
+    int error;
+    int fd = -1;
+
+    /* Determine location. */
+    client = xmalloc(sizeof *client);
+    if (path[0] == '/') {
+        client->connect_path = xstrdup(path);
+    } else {
+        client->connect_path = xasprintf("%s/%s", ofp_rundir, path);
+    }
+    client->bind_path = xasprintf("/tmp/vlog.%ld.%d",
+                                  (long int) getpid(), counter++);
+
+    /* Open socket. */
+    fd = make_unix_socket(SOCK_STREAM, false, false,
+                          client->bind_path, client->connect_path);
+    if (fd < 0) {
+        error = -fd;
+        goto error;
+    }
+
+    /* Bind socket to stream. */
+    client->stream = fdopen(fd, "r+");
+    if (!client->stream) {
+        error = errno;
+        VLOG_WARN("%s: fdopen failed (%s)",
+                  client->connect_path, strerror(error));
+        goto error;
+    }
+    *clientp = client;
+    return 0;
+
+error:
+    if (fd >= 0) {
+        close(fd);
+    }
+    free(client->connect_path);
+    free(client->bind_path);
+    free(client);
+    *clientp = NULL;
+    return error;
+}
+
+/* Destroys 'client'. */
+void
+unixctl_client_destroy(struct unixctl_client *client)
+{
+    if (client) {
+        unlink(client->bind_path);
+        fatal_signal_remove_file_to_unlink(client->bind_path);
+        free(client->bind_path);
+        free(client->connect_path);
+        fclose(client->stream);
+        free(client);
+    }
+}
+
+/* Sends 'request' to the server socket and waits for a reply.  Returns 0 if
+ * successful, otherwise to a positive errno value.  If successful, sets
+ * '*reply' to the reply, which the caller must free, otherwise to NULL. */
+int
+unixctl_client_transact(struct unixctl_client *client,
+                        const char *request,
+                        int *reply_code, char **reply_body)
+{
+    struct ds line = DS_EMPTY_INITIALIZER;
+    struct ds reply = DS_EMPTY_INITIALIZER;
+    int error;
+
+    /* Send 'request' to server.  Add a new-line if 'request' didn't end in
+     * one. */
+    fputs(request, client->stream);
+    if (request[0] == '\0' || request[strlen(request) - 1] != '\n') {
+        putc('\n', client->stream);
+    }
+    if (ferror(client->stream)) {
+        VLOG_WARN("error sending request to %s: %s",
+                  client->connect_path, strerror(errno));
+        return errno;
+    }
+
+    /* Wait for response. */
+    *reply_code = -1;
+    for (;;) {
+        const char *s;
+
+        error = ds_get_line(&line, client->stream);
+        if (error) {
+            VLOG_WARN("error reading reply from %s: %s",
+                      client->connect_path,
+                      (error == EOF ? "unexpected end of file"
+                       : strerror(error)));
+            goto error;
+        }
+
+        s = ds_cstr(&line);
+        if (*reply_code == -1) {
+            if (!isdigit(s[0]) || !isdigit(s[1]) || !isdigit(s[2])) {
+                VLOG_WARN("reply from %s does not start with 3-digit code",
+                          client->connect_path);
+                error = EPROTO;
+                goto error;
+            }
+            sscanf(s, "%3d", reply_code);
+        } else {
+            if (s[0] == '.') {
+                if (s[1] == '\0') {
+                    break;
+                }
+                s++;
+            }
+            ds_put_cstr(&reply, s);
+            ds_put_char(&reply, '\n');
+        }
+    }
+    *reply_body = ds_cstr(&reply);
+    ds_destroy(&line);
+    return 0;
+
+error:
+    ds_destroy(&line);
+    ds_destroy(&reply);
+    *reply_code = 0;
+    *reply_body = NULL;
+    return error == EOF ? EPROTO : error;
+}
+
+/* Returns the path of the server socket to which 'client' is connected.  The
+ * caller must not modify or free the returned string. */
+const char *
+unixctl_client_target(const struct unixctl_client *client)
+{
+    return client->connect_path;
+}
diff --git a/lib/unixctl.h b/lib/unixctl.h
new file mode 100644 (file)
index 0000000..5812059
--- /dev/null
@@ -0,0 +1,61 @@
+/* Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford
+ * Junior University
+ * 
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * 
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#ifndef UNIXCTL_H
+#define UNIXCTL_H 1
+
+/* Server for Unix domain socket control connection. */
+struct unixctl_server;
+int unixctl_server_create(const char *path, struct unixctl_server **);
+void unixctl_server_run(struct unixctl_server *);
+void unixctl_server_wait(struct unixctl_server *);
+void unixctl_server_destroy(struct unixctl_server *);
+
+/* Client for Unix domain socket control connection. */
+struct unixctl_client;
+int unixctl_client_create(const char *path, struct unixctl_client **);
+void unixctl_client_destroy(struct unixctl_client *);
+int unixctl_client_transact(struct unixctl_client *,
+                            const char *request,
+                            int *reply_code, char **reply_body);
+const char *unixctl_client_target(const struct unixctl_client *);
+
+/* Command registration. */
+struct unixctl_conn;
+void unixctl_command_register(const char *name,
+                              void (*cb)(struct unixctl_conn *,
+                                         const char *args));
+void unixctl_command_reply(struct unixctl_conn *, int code,
+                           const char *body);
+
+#endif /* unixctl.h */
index f79fe2b5907f9b7b94c2ac3299ba9e8b2f1e9577..e6b738111994761936dcb26e9609101f84773926 100644 (file)
@@ -45,13 +45,13 @@ VLOG_MODULE(switch)
 VLOG_MODULE(terminal)
 VLOG_MODULE(timeval)
 VLOG_MODULE(socket_util)
+VLOG_MODULE(unixctl)
 VLOG_MODULE(vconn_tcp)
 VLOG_MODULE(vconn_ssl)
 VLOG_MODULE(vconn_stream)
 VLOG_MODULE(vconn_unix)
 VLOG_MODULE(vconn)
 VLOG_MODULE(vlog)
-VLOG_MODULE(vlog_socket)
 VLOG_MODULE(wcelim)
 VLOG_MODULE(vswitchd)
 VLOG_MODULE(xenserver)
diff --git a/lib/vlog-socket.c b/lib/vlog-socket.c
deleted file mode 100644 (file)
index 11bf225..0000000
+++ /dev/null
@@ -1,482 +0,0 @@
-/* Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford
- * Junior University
- * 
- * We are making the OpenFlow specification and associated documentation
- * (Software) available for public use and benefit with the expectation
- * that others will use, modify and enhance the Software and contribute
- * those enhancements back to the community. However, since we would
- * like to make the Software available for broadest use, with as few
- * restrictions as possible permission is hereby granted, free of
- * charge, to any person obtaining a copy of this Software to deal in
- * the Software under the copyrights without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- * 
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- * 
- * The name and trademarks of copyright holder(s) may NOT be used in
- * advertising or publicity pertaining to the Software or any
- * derivatives without specific, written prior permission.
- */
-
-#include <config.h>
-#include "vlog-socket.h"
-#include <ctype.h>
-#include <errno.h>
-#include <sys/un.h>
-#include <fcntl.h>
-#include <poll.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include "daemon.h"
-#include "fatal-signal.h"
-#include "poll-loop.h"
-#include "socket-util.h"
-#include "timeval.h"
-#include "util.h"
-
-#ifndef SCM_CREDENTIALS
-#include <time.h>
-#endif
-
-#define THIS_MODULE VLM_vlog_socket
-#include "vlog.h"
-\f
-/* Server for Vlog control connection. */
-struct vlog_server {
-    struct poll_waiter *waiter;
-    char *path;
-    int fd;
-};
-
-static void poll_server(int fd, short int events, void *server_);
-
-/* Start listening for connections from clients and processing their
- * requests.  'path' may be:
- *
- *      - NULL, in which case the default socket path is used.  (Only one
- *        Vlog_server_socket per process can use the default path.)
- *
- *      - A name that does not start with '/', in which case it is appended to
- *        the default socket path.
- *
- *      - An absolute path (starting with '/') that gives the exact name of
- *        the Unix domain socket to listen on.
- *
- * A program that (optionally) daemonizes itself should call this function
- * *after* daemonization, so that the socket name contains the pid of the
- * daemon instead of the pid of the program that exited.  (Otherwise, "vlogconf
- * --target <program>.pid" will fail.)
- *
- * Returns 0 if successful, otherwise a positive errno value.  If successful,
- * sets '*serverp' to the new vlog_server, otherwise to NULL. */
-int
-vlog_server_listen(const char *path, struct vlog_server **serverp)
-{
-    struct vlog_server *server = xmalloc(sizeof *server);
-
-    if (path && path[0] == '/') {
-        server->path = xstrdup(path);
-    } else {
-        server->path = xasprintf("/tmp/vlogs.%ld%s",
-                                 (long int) getpid(), path ? path : "");
-    }
-
-    server->fd = make_unix_socket(SOCK_DGRAM, true, true, server->path, NULL);
-    if (server->fd < 0) {
-        int fd = server->fd;
-        fprintf(stderr, "Could not initialize vlog configuration socket: %s\n",
-                strerror(-server->fd));
-        free(server->path);
-        free(server);
-        if (serverp) {
-            *serverp = NULL; 
-        }
-        return fd;
-    }
-
-    server->waiter = poll_fd_callback(server->fd, POLLIN, poll_server, server);
-
-    if (serverp) {
-        *serverp = server; 
-    }
-    return 0;
-}
-
-/* Destroys 'server' and stops listening for connections. */
-void
-vlog_server_close(struct vlog_server *server)
-{
-    if (server) {
-        poll_cancel(server->waiter);
-        close(server->fd);
-        unlink(server->path);
-        fatal_signal_remove_file_to_unlink(server->path);
-        free(server->path);
-        free(server);
-    }
-}
-
-static int
-recv_with_creds(const struct vlog_server *server,
-                char *cmd_buf, size_t cmd_buf_size,
-                struct sockaddr_un *un, socklen_t *un_len)
-{
-#ifdef SCM_CREDENTIALS
-    /* Read a message and control messages from 'fd'.  */
-    char cred_buf[CMSG_SPACE(sizeof(struct ucred))];
-    ssize_t n;
-    struct iovec iov;
-    struct msghdr msg;
-    struct ucred* cred;
-    struct cmsghdr* cmsg;
-
-    iov.iov_base = cmd_buf;
-    iov.iov_len = cmd_buf_size - 1;
-
-    memset(&msg, 0, sizeof msg);
-    msg.msg_name = un;
-    msg.msg_namelen = sizeof *un;
-    msg.msg_iov = &iov;
-    msg.msg_iovlen = 1;
-    msg.msg_control = cred_buf;
-    msg.msg_controllen = sizeof cred_buf;
-
-    n = recvmsg(server->fd, &msg, 0);
-    *un_len = msg.msg_namelen;
-    if (n < 0) {
-        return errno;
-    }
-    cmd_buf[n] = '\0';
-
-    /* Ensure that the message has credentials ensuring that it was sent
-     * from the same user who started us, or by root. */
-    cred = NULL;
-    for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
-         cmsg = CMSG_NXTHDR(&msg, cmsg)) {
-        if (cmsg->cmsg_level == SOL_SOCKET
-            && cmsg->cmsg_type == SCM_CREDENTIALS) {
-            cred = (struct ucred *) CMSG_DATA(cmsg);
-        } else if (cmsg->cmsg_level == SOL_SOCKET
-                   && cmsg->cmsg_type == SCM_RIGHTS) {
-            /* Anyone can send us fds.  If we don't close them, then that's
-             * a DoS: the sender can overflow our fd table. */
-            int* fds = (int *) CMSG_DATA(cmsg);
-            size_t n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof *fds;
-            size_t i;
-            for (i = 0; i < n_fds; i++) {
-                close(fds[i]);
-            }
-        }
-    }
-    if (!cred) {
-        fprintf(stderr, "vlog: config message lacks credentials\n");
-        return -1;
-    } else if (cred->uid && cred->uid != getuid()) {
-        fprintf(stderr, "vlog: config message uid=%ld is not 0 or %ld\n",
-                (long int) cred->uid, (long int) getuid());
-        return -1;
-    }
-
-    return 0;
-#else /* !SCM_CREDENTIALS */
-    socklen_t len;
-    ssize_t n;
-    struct stat s;
-    time_t recent;
-
-    /* Receive a message. */
-    len = sizeof *un;
-    n = recvfrom(server->fd, cmd_buf, cmd_buf_size - 1, 0,
-                 (struct sockaddr *) un, &len);
-    *un_len = len;
-    if (n < 0) {
-        return errno;
-    }
-    cmd_buf[n] = '\0';
-
-    len -= offsetof(struct sockaddr_un, sun_path);
-    un->sun_path[len] = '\0';
-    if (stat(un->sun_path, &s) < 0) {
-        fprintf(stderr, "vlog: config message from inaccessible socket: %s\n",
-                strerror(errno));
-        return -1;
-    }
-    if (!S_ISSOCK(s.st_mode)) {
-        fprintf(stderr, "vlog: config message not from a socket\n");
-        return -1;
-    }
-    recent = time_now() - 30;
-    if (s.st_atime < recent || s.st_ctime < recent || s.st_mtime < recent) {
-        fprintf(stderr, "vlog: config socket too old\n");
-        return -1;
-    }
-    if (s.st_uid && s.st_uid != getuid()) {
-        fprintf(stderr, "vlog: config message uid=%ld is not 0 or %ld\n",
-                (long int) s.st_uid, (long int) getuid());
-        return -1;
-    }
-    return 0;
-#endif /* !SCM_CREDENTIALS */
-}
-
-/* Processes incoming requests for 'server'. */
-static void
-poll_server(int fd UNUSED, short int events UNUSED, void *server_)
-{
-    struct vlog_server *server = server_;
-    for (;;) {
-        char cmd_buf[512];
-        struct sockaddr_un un;
-        socklen_t un_len;
-        char *reply;
-        int error;
-
-        error = recv_with_creds(server, cmd_buf, sizeof cmd_buf, &un, &un_len);
-        if (error > 0) {
-            if (error != EAGAIN && error != EWOULDBLOCK) {
-                fprintf(stderr, "vlog: reading configuration socket: %s",
-                        strerror(errno));
-            }
-            break;
-        } else if (error < 0) {
-            continue;
-        }
-
-        /* Process message and send reply. */
-        if (!strncmp(cmd_buf, "set ", 4)) {
-            char *msg = vlog_set_levels_from_string(cmd_buf + 4);
-            reply = msg ? msg : xstrdup("ack");
-        } else if (!strcmp(cmd_buf, "list")) {
-            reply = vlog_get_levels();
-        } else if (!strcmp(cmd_buf, "reopen")) {
-            int error = vlog_reopen_log_file();
-            reply = (error
-                     ? xasprintf("could not reopen log file \"%s\": %s",
-                                 vlog_get_log_file(), strerror(error))
-                     : xstrdup("ack"));
-        } else {
-            reply = xstrdup("nak");
-        }
-        sendto(server->fd, reply, strlen(reply), 0,
-               (struct sockaddr*) &un, un_len);
-        free(reply);
-    }
-    server->waiter = poll_fd_callback(server->fd, POLLIN, poll_server, server);
-}
-\f
-/* Client for Vlog control connection. */
-
-struct vlog_client {
-    char *connect_path;
-    char *bind_path;
-    int fd;
-};
-
-/* Connects to a Vlog server socket.  'path' may be:
- *
- *      - A string that starts with a PID.  If a non-null, non-absolute name
- *        was passed to Vlog_server_socket::listen(), then it must follow the
- *        PID in 'path'.
- *
- *      - An absolute path (starting with '/') to a Vlog server socket or a
- *        pidfile.  If it is a pidfile, the pidfile will be read and translated
- *        into a Vlog server socket file name.
- *
- *      - A relative path, which is translated into a pidfile name and then
- *        treated as above.
- *
- * Returns 0 if successful, otherwise a positive errno value.  If successful,
- * sets '*clientp' to the new vlog_client, otherwise to NULL. */
-int
-vlog_client_connect(const char *path, struct vlog_client **clientp)
-{
-    static int counter;
-    struct vlog_client *client;
-    struct stat s;
-    int error;
-
-    client = xmalloc(sizeof *client);
-    if (path[0] == '/') {
-        client->connect_path = xstrdup(path);
-    } else if (isdigit((unsigned char) path[0])) {
-        client->connect_path = xasprintf("/tmp/vlogs.%s", path);
-    } else {
-        client->connect_path = make_pidfile_name(path);
-    }
-    client->bind_path = NULL;
-
-    if (stat(client->connect_path, &s)) {
-        error = errno;
-        VLOG_WARN("could not stat \"%s\": %s",
-                  client->connect_path, strerror(error));
-        goto error;
-    } else if (S_ISREG(s.st_mode)) {
-        pid_t pid = read_pidfile(client->connect_path);
-        if (pid < 0) {
-            error = -pid;
-            VLOG_WARN("could not read pidfile \"%s\": %s",
-                      client->connect_path, strerror(error));
-            goto error;
-        }
-        free(client->connect_path);
-        client->connect_path = xasprintf("/tmp/vlogs.%ld", (long int) pid);
-    }
-    client->bind_path = xasprintf("/tmp/vlog.%ld.%d",
-                                  (long int) getpid(), counter++);
-    client->fd = make_unix_socket(SOCK_DGRAM, false, false,
-                                  client->bind_path, client->connect_path);
-    if (client->fd < 0) {
-        error = -client->fd;
-        goto error;
-    }
-    *clientp = client;
-    return 0;
-
-error:
-    free(client->connect_path);
-    free(client->bind_path);
-    free(client);
-    *clientp = NULL;
-    return error;
-}
-
-/* Destroys 'client'. */
-void
-vlog_client_close(struct vlog_client *client)
-{
-    if (client) {
-        unlink(client->bind_path);
-        fatal_signal_remove_file_to_unlink(client->bind_path);
-        free(client->bind_path);
-        free(client->connect_path);
-        close(client->fd);
-        free(client);
-    }
-}
-
-/* Sends 'request' to the server socket that 'client' is connected to.  Returns
- * 0 if successful, otherwise a positive errno value. */
-int
-vlog_client_send(struct vlog_client *client, const char *request)
-{
-#ifdef SCM_CREDENTIALS
-    struct ucred cred;
-    struct iovec iov;
-    char buf[CMSG_SPACE(sizeof cred)];
-    struct msghdr msg;
-    struct cmsghdr* cmsg;
-    ssize_t nbytes;
-
-    cred.pid = getpid();
-    cred.uid = getuid();
-    cred.gid = getgid();
-
-    iov.iov_base = (void*) request;
-    iov.iov_len = strlen(request);
-
-    memset(&msg, 0, sizeof msg);
-    msg.msg_iov = &iov;
-    msg.msg_iovlen = 1;
-    msg.msg_control = buf;
-    msg.msg_controllen = sizeof buf;
-
-    cmsg = CMSG_FIRSTHDR(&msg);
-    cmsg->cmsg_level = SOL_SOCKET;
-    cmsg->cmsg_type = SCM_CREDENTIALS;
-    cmsg->cmsg_len = CMSG_LEN(sizeof cred);
-    memcpy(CMSG_DATA(cmsg), &cred, sizeof cred);
-    msg.msg_controllen = cmsg->cmsg_len;
-
-    nbytes = sendmsg(client->fd, &msg, 0);
-#else /* !SCM_CREDENTIALS */
-    ssize_t nbytes = send(client->fd, request, strlen(request), 0);
-#endif /* !SCM_CREDENTIALS */
-    if (nbytes > 0) {
-        return nbytes == strlen(request) ? 0 : ENOBUFS;
-    } else {
-        return errno;
-    }
-}
-
-/* Attempts to receive a response from the server socket that 'client' is
- * connected to.  Returns 0 if successful, otherwise a positive errno value.
- * If successful, sets '*reply' to the reply, which the caller must free,
- * otherwise to NULL. */
-int
-vlog_client_recv(struct vlog_client *client, char **reply)
-{
-    struct pollfd pfd;
-    int nfds;
-    char buffer[65536];
-    ssize_t nbytes;
-
-    *reply = NULL;
-
-    pfd.fd = client->fd;
-    pfd.events = POLLIN;
-    nfds = time_poll(&pfd, 1, 1000);
-    if (nfds == 0) {
-        return ETIMEDOUT;
-    } else if (nfds < 0) {
-        return -nfds;
-    }
-
-    nbytes = read(client->fd, buffer, sizeof buffer - 1);
-    if (nbytes < 0) {
-        return errno;
-    } else {
-        buffer[nbytes] = '\0';
-        *reply = xstrdup(buffer);
-        return 0;
-    }
-}
-
-/* Sends 'request' to the server socket and waits for a reply.  Returns 0 if
- * successful, otherwise to a positive errno value.  If successful, sets
- * '*reply' to the reply, which the caller must free, otherwise to NULL. */
-int
-vlog_client_transact(struct vlog_client *client,
-                     const char *request, char **reply)
-{
-    int i;
-
-    /* Retry up to 3 times. */
-    for (i = 0; i < 3; ++i) {
-        int error = vlog_client_send(client, request);
-        if (error) {
-            *reply = NULL;
-            return error;
-        }
-        error = vlog_client_recv(client, reply);
-        if (error != ETIMEDOUT) {
-            return error;
-        }
-    }
-    *reply = NULL;
-    return ETIMEDOUT;
-}
-
-/* Returns the path of the server socket to which 'client' is connected.  The
- * caller must not modify or free the returned string. */
-const char *
-vlog_client_target(const struct vlog_client *client)
-{
-    return client->connect_path;
-}
diff --git a/lib/vlog-socket.h b/lib/vlog-socket.h
deleted file mode 100644 (file)
index ac25f63..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
- * Junior University
- * 
- * We are making the OpenFlow specification and associated documentation
- * (Software) available for public use and benefit with the expectation
- * that others will use, modify and enhance the Software and contribute
- * those enhancements back to the community. However, since we would
- * like to make the Software available for broadest use, with as few
- * restrictions as possible permission is hereby granted, free of
- * charge, to any person obtaining a copy of this Software to deal in
- * the Software under the copyrights without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- * 
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- * 
- * The name and trademarks of copyright holder(s) may NOT be used in
- * advertising or publicity pertaining to the Software or any
- * derivatives without specific, written prior permission.
- */
-
-#ifndef VLOG_SOCKET_H
-#define VLOG_SOCKET_H 1
-
-/* Server for Vlog control connection. */
-struct vlog_server;
-int vlog_server_listen(const char *path, struct vlog_server **);
-void vlog_server_close(struct vlog_server *);
-
-/* Client for Vlog control connection. */
-struct vlog_client;
-int vlog_client_connect(const char *path, struct vlog_client **);
-void vlog_client_close(struct vlog_client *);
-int vlog_client_send(struct vlog_client *, const char *request);
-int vlog_client_recv(struct vlog_client *, char **reply);
-int vlog_client_transact(struct vlog_client *,
-                         const char *request, char **reply);
-const char *vlog_client_target(const struct vlog_client *);
-
-#endif /* vlog-socket.h */
index 63d274ccb9164000a34b0d46ba19131595393953..c6452ad65962a9011327bc74171b83f3fd035138 100644 (file)
@@ -47,6 +47,7 @@
 #include "dynamic-string.h"
 #include "sat-math.h"
 #include "timeval.h"
+#include "unixctl.h"
 #include "util.h"
 
 #define THIS_MODULE VLM_vlog
@@ -400,6 +401,37 @@ vlog_set_verbosity(const char *arg)
     }
 }
 
+static void
+vlog_unixctl_set(struct unixctl_conn *conn, const char *args)
+{
+    char *msg = vlog_set_levels_from_string(args);
+    unixctl_command_reply(conn, msg ? 501 : 202, msg);
+    free(msg);
+}
+
+static void
+vlog_unixctl_list(struct unixctl_conn *conn, const char *args UNUSED)
+{
+    char *msg = vlog_get_levels();
+    unixctl_command_reply(conn, 200, msg);
+    free(msg);
+}
+
+static void
+vlog_unixctl_reopen(struct unixctl_conn *conn, const char *args UNUSED)
+{
+    if (log_file_name) {
+        int error = vlog_reopen_log_file();
+        if (error) {
+            unixctl_command_reply(conn, 503, strerror(errno));
+        } else {
+            unixctl_command_reply(conn, 202, NULL);
+        }
+    } else {
+        unixctl_command_reply(conn, 403, "Logging to file not configured");
+    }
+}
+
 /* Initializes the logging subsystem. */
 void
 vlog_init(void) 
@@ -419,6 +451,10 @@ vlog_init(void)
         strftime(s, sizeof s, "%a, %d %b %Y %H:%M:%S %z", &tm);
         VLOG_ERR("current time is negative: %s (%ld)", s, (long int) now);
     }
+
+    unixctl_command_register("vlog/set", vlog_unixctl_set);
+    unixctl_command_register("vlog/list", vlog_unixctl_list);
+    unixctl_command_register("vlog/reopen", vlog_unixctl_reopen);
 }
 
 /* Closes the logging subsystem. */
index d9d44b9da2d5a6658df91a291ab5392d15c8d41f..334e3196d656279308fd4b78e7025fffbe08246d 100644 (file)
 #include "status.h"
 #include "svec.h"
 #include "timeval.h"
+#include "unixctl.h"
 #include "util.h"
 #include "vconn-ssl.h"
 #include "vconn.h"
-#include "vlog-socket.h"
 
 #include "vlog.h"
 #define THIS_MODULE VLM_secchan
@@ -131,6 +131,7 @@ static void usage(void) NO_RETURN;
 int
 main(int argc, char *argv[])
 {
+    struct unixctl_server *unixctl;
     struct ofproto *ofproto;
     struct ofsettings s;
     int error;
@@ -146,9 +147,9 @@ main(int argc, char *argv[])
     daemonize();
 
     /* Start listening for vlogconf requests. */
-    error = vlog_server_listen(NULL, NULL);
+    error = unixctl_server_create(NULL, &unixctl);
     if (error) {
-        ofp_fatal(error, "Could not listen for vlog connections");
+        ofp_fatal(error, "Could not listen for unixctl connections");
     }
 
     VLOG_INFO("OpenFlow reference implementation version %s", VERSION BUILDNR);
@@ -213,7 +214,10 @@ main(int argc, char *argv[])
         if (error) {
             ofp_fatal(error, "unrecoverable datapath error");
         }
+        unixctl_server_run(unixctl);
+
         ofproto_wait(ofproto);
+        unixctl_server_wait(unixctl);
         poll_block();
     }
 
index 55f40429f019c6c2b50feb94814d610bed7b3e6b..48627a34a486613d08af7b8fe4012c60cf5085f7 100644 (file)
@@ -48,8 +48,8 @@
 #include "netdev.h"
 #include "poll-loop.h"
 #include "timeval.h"
+#include "unixctl.h"
 #include "util.h"
-#include "vlog-socket.h"
 
 #include "vlog.h"
 #define THIS_MODULE VLM_ofp_discover
@@ -88,6 +88,7 @@ static bool validate_dhcp_offer(const struct dhcp_msg *, void *aux);
 int
 main(int argc, char *argv[])
 {
+    struct unixctl_server *unixctl;
     int retval;
     int i;
 
@@ -129,9 +130,9 @@ main(int argc, char *argv[])
         ofp_fatal(0, "%s: %s", accept_controller_re, buffer);
     }
 
-    retval = vlog_server_listen(NULL, NULL);
+    retval = unixctl_server_create(NULL, &unixctl);
     if (retval) {
-        ofp_fatal(retval, "Could not listen for vlog connections");
+        ofp_fatal(retval, "Could not listen for unixctl connections");
     }
 
     die_if_already_running();
@@ -205,10 +206,12 @@ main(int argc, char *argv[])
                 }
             }
         }
+        unixctl_server_run(unixctl);
         for (i = 0; i < n_ifaces; i++) {
             struct iface *iface = &ifaces[i];
             dhclient_wait(iface->dhcp);
         }
+        unixctl_server_wait(unixctl);
         fatal_signal_unblock();
         poll_block();
     }
index 0baf6d6fb74706ecdda252a8833f6599ec4b0d43..7cbd374e4bc4398b1372197372b5061e3c33a08a 100644 (file)
@@ -1,4 +1,10 @@
-.TH vlogconf 8 "June 2008" "OpenFlow" "OpenFlow Manual"
+.\" -*- nroff -*-
+.de IQ
+.  br
+.  ns
+.  IP "\\$1"
+..
+.TH vlogconf 8 "April 2009" "OpenFlow" "OpenFlow Manual"
 .ds PN vlogconf
 
 .SH NAME
@@ -9,7 +15,7 @@ vlogconf \- configuration utility for OpenFlow logging in userspace
 .sp 1
 The available \fItarget\fR options are:
 .br
-[\fB-a\fR | \fB--all\fR] [\fB-t\fR \fIpid\fR | \fB--target=\fIpid\fR]
+[\fB-t\fR \fIpid\fR | \fB--target=\fIpid\fR]
 .sp 1
 The available \fIaction\fR options are:
 .br
@@ -17,6 +23,7 @@ The available \fIaction\fR options are:
 \fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]] |
 \fB--set=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]]
 [\fB-r\fR | \fB--reopen\fR]
+[\fB-e\fR | \fB--execute=\fIcommand\fR]
 
 .SH DESCRIPTION
 The \fBvlogconf\fR program configures the logging system used by 
@@ -24,48 +31,24 @@ OpenFlow userspace programs.  The logging configuration may be modified
 while OpenFlow programs are running.
 
 \fBvlogconf\fR applies one or more actions to each of one or more
-target processes.  Targets may be specified as:
+target processes.  Targets may be specified using:
 
-.TP
-\fB-a\fR, \fB--all\fR
-All running processes that \fBvlogconf\fR can control.
-
-.TP
-\fB-t \fItarget\fR, \fB--target=\fItarget\fR
-The specified \fItarget\fR, which must take one of the following forms:
-
-.RS
-.IP \(bu
-A PID (process ID).
-
-.IP \(bu
-An absolute path (beginning with `/') to the Unix domain socket for a
-\fBvlogconf\fR-controllable process.
-
-.IP \(bu
-An absolute path (beginning with `/') to a pidfile (created by, e.g.,
-passing the \fB-P\fR or \fB--pidfile\fR option to one of the OpenFlow
-programs).  
-
-.IP \(bu
-None of the above, in which case \fItarget\fR prefixed by
-\fB@RUNDIR@/\fR must match one of the cases for absolute paths listed
-above.  (The default name for a program's pidfile is
-\fB@RUNDIR@/\fIprogram\fB.pid\fR, so this means that, say,
-\fBsecchan\fR's default pidfile may be referred to simply as
-\fBsecchan.pid\fR.)
-.RE
+.IP "\fB-t \fIsocket\fR"
+.IQ "\fB--target=\fIsocket\fR"
+The specified \fIsocket\fR must be the name of a Unix domain socket
+for a \fBvlogconf\fR-controllable process.  If \fIsocket\fR does not
+begin with \fB/\fR, it is treated as relative to \fB@RUNDIR@\fR.
 
 .PP
 The available actions are:
 
-.TP
-\fB-l\fR, \fB--list\fR
+.IP "\fB-l\fR"
+.IQ "\fB--list\fR"
 Print the list of known modules and their current logging levels to
 stdout.
 
-.TP
-\fB-s\fR \fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]], \fB--set=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]
+.IP "\fB-s\fR \fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]"
+.IQ "\fB--set=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]"
 
 Sets the logging level for \fImodule\fR in \fIfacility\fR to
 \fIlevel\fR.  The \fImodule\fR may be any valid module name (as
@@ -79,8 +62,8 @@ logging levels for both facilities.  If it is omitted,
 minimum severity of a message for it to be logged.  If it is omitted,
 \fIlevel\fR defaults to \fBdbg\fR.
 
-.TP
-\fB-s PATTERN:\fIfacility\fB:\fIpattern\fR, \fB--set=PATTERN:\fIfacility\fB:\fIpattern\fR
+.IP "\fB-s PATTERN:\fIfacility\fB:\fIpattern\fR"
+.IQ "\fB--set=PATTERN:\fIfacility\fB:\fIpattern\fR"
 
 Sets the log pattern for \fIfacility\fR to \fIpattern\fR.  Each time a
 message is logged to \fIfacility\fR, \fIpattern\fR determines the
@@ -89,53 +72,42 @@ literally to the log, but special escapes beginning with \fB%\fR are
 expanded as follows:
 
 .RS
-.TP
-\fB%A\fR
+.IP \fB%A\fR
 The name of the application logging the message, e.g. \fBsecchan\fR.
 
-.TP
-\fB%c\fR
+.IP \fB%c\fR
 The name of the module (as shown by \fBvlogconf --list\fR) logging
 the message.
 
-.TP
-\fB%d\fR
+.IP \fB%d\fR
 The current date and time in ISO 8601 format (YYYY-MM-DD HH:MM:SS).
 
-.TP
-\fB%d{\fIformat\fB}\fR
+.IP \fB%d{\fIformat\fB}\fR
 The current date and time in the specified \fIformat\fR, which takes
 the same format as the \fItemplate\fR argument to \fBstrftime\fR(3).
 
-.TP
-\fB%m\fR
+.IP \fB%m\fR
 The message being logged.
 
-.TP
-\fB%N\fR
+.IP \fB%N\fR
 A serial number for this message within this run of the program, as a
 decimal number.  The first message a program logs has serial number 1,
 the second one has serial number 2, and so on.
 
-.TP
-\fB%n\fR
+.IP \fB%n\fR
 A new-line.
 
-.TP
-\fB%p\fR
+.IP \fB%p\fR
 The level at which the message is logged, e.g. \fBDBG\fR.
 
-.TP
-\fB%P\fR
+.IP \fB%P\fR
 The program's process ID (pid), as a decimal number.
 
-.TP
-\fB%r\fR
+.IP \fB%r\fR
 The number of milliseconds elapsed from the start of the application
 to the time the message was logged.
 
-.TP
-\fB%%\fR
+.IP \fB%%\fR
 A literal \fB%\fR.
 .RE
 
@@ -144,18 +116,15 @@ A few options may appear between the \fB%\fR and the format specifier
 character, in this order:
 
 .RS
-.TP
-\fB-\fR
+.IP \fB-\fR
 Left justify the escape's expansion within its field width.  Right
 justification is the default.
 
-.TP
-\fB0\fR
+.IP \fB0\fR
 Pad the field to the field width with \fB0\fRs.  Padding with spaces
 is the default.
 
-.TP
-\fIwidth\fR
+.IP \fIwidth\fR
 A number specifies the minimum field width.  If the escape expands to
 fewer characters than \fIwidth\fR then it is padded to fill the field
 width.  (A field wider than \fIwidth\fR is not truncated to fit.)
@@ -165,12 +134,18 @@ width.  (A field wider than \fIwidth\fR is not truncated to fit.)
 The default pattern for console output is \fB%d{%b %d
 %H:%M:%S}|%05N|%c|%p|%m\fR; for syslog output, \fB%05N|%c|%p|%m\fR.
 
-.TP
-\fB-r\fR, \fB--reopen\fR
+.IP \fB-r\fR
+.IQ \fB--reopen\fR
 Causes the target application to close and reopen its log file.  (This
 is useful after rotating log files, to cause a new log file to be
 used.)
 
+.IP "\fB-e \fIcommand\fR"
+.IQ "\fB--execute=\fIcommand\fR"
+Passes the specified \fIcommand\fR literally to the target application
+and prints its response to stdout, if successful, or to stderr if an
+error occurs.  Use \fB-e help\fR to print a list of available commands.
+
 .SH OPTIONS
 
 .so lib/common.man
index 5c48f724549e8ec1d83bb7e4c85f467ab83b64da..1ce01bda0ef726a09e60fba97bd9dfc7b6fe8d5d 100644 (file)
 #include "command-line.h"
 #include "compiler.h"
 #include "timeval.h"
+#include "unixctl.h"
 #include "util.h"
-#include "vlog-socket.h"
 
 static void
 usage(char *prog_name, int exit_code)
 {
     printf("Usage: %s [TARGET] [ACTION...]\n"
            "Targets:\n"
-           "  -a, --all            Apply to all targets (default)\n"
-           "  -t, --target=TARGET  Specify target program, as a pid, a\n"
-           "                       pidfile, or an absolute path to a Unix\n"
-           "                       domain socket\n"
+           "  -t, --target=TARGET  Path to Unix domain socket\n"
            "Actions:\n"
            "  -l, --list         List current settings\n"
            "  -s, --set=MODULE[:FACILITY[:LEVEL]]\n"
@@ -64,47 +61,66 @@ usage(char *prog_name, int exit_code)
            "        FACILITY may be 'syslog', 'console', 'file', or 'ANY' (default)\n"
            "        LEVEL may be 'emer', 'err', 'warn', 'info', or 'dbg' (default)\n"
            "  -r, --reopen       Make the program reopen its log file\n"
+           "  -e, --execute=COMMAND  Execute control COMMAND and print its output\n"
            "  -h, --help         Print this helpful information\n",
            prog_name);
     exit(exit_code);
 }
 
 static char *
-transact(struct vlog_client *client, const char *request, bool *ok)
+transact(struct unixctl_client *client, const char *request, bool *ok)
 {
+    int code;
     char *reply;
-    int error = vlog_client_transact(client, request, &reply);
+    int error = unixctl_client_transact(client, request, &code, &reply);
     if (error) {
         fprintf(stderr, "%s: transaction error: %s\n",
-                vlog_client_target(client), strerror(error));
+                unixctl_client_target(client), strerror(error));
         *ok = false;
+        return xstrdup("");
+    } else {
+        if (code / 100 != 2) {
+            fprintf(stderr, "%s: server returned reply code %03d\n",
+                    unixctl_client_target(client), code);
+        }
+        return reply;
     }
-    return reply ? reply : xstrdup("");
 }
 
 static void
-transact_ack(struct vlog_client *client, const char* request, bool *ok)
+transact_ack(struct unixctl_client *client, const char *request, bool *ok)
 {
+    free(transact(client, request, ok));
+}
+
+static void
+execute_command(struct unixctl_client *client, const char *request, bool *ok)
+{
+    int code;
     char *reply;
-    int error = vlog_client_transact(client, request, &reply);
+    int error = unixctl_client_transact(client, request, &code, &reply);
     if (error) {
         fprintf(stderr, "%s: transaction error: %s\n",
-                vlog_client_target(client), strerror(error));
-        *ok = false;
-    } else if (strcmp(reply, "ack")) {
-        fprintf(stderr, "Received unexpected reply from %s: %s\n",
-                vlog_client_target(client), reply);
+                unixctl_client_target(client), strerror(error));
         *ok = false;
+    } else {
+        if (code / 100 != 2) {
+            fprintf(stderr, "%s: server returned reply code %03d\n",
+                    unixctl_client_target(client), code);
+            fputs(reply, stderr);
+            *ok = false;
+        } else {
+            fputs(reply, stdout);
+        }
     }
-    free(reply);
 }
 
 static void
-add_target(struct vlog_client ***clients, size_t *n_clients,
+add_target(struct unixctl_client ***clients, size_t *n_clients,
            const char *path, bool *ok)
 {
-    struct vlog_client *client;
-    int error = vlog_client_connect(path, &client);
+    struct unixctl_client *client;
+    int error = unixctl_client_create(path, &client);
     if (error) {
         fprintf(stderr, "Error connecting to \"%s\": %s\n",
                 path, strerror(error));
@@ -116,33 +132,10 @@ add_target(struct vlog_client ***clients, size_t *n_clients,
     }
 }
 
-static void
-add_all_targets(struct vlog_client ***clients, size_t *n_clients, bool *ok)
-{
-    DIR *directory;
-    struct dirent* de;
-
-    directory = opendir("/tmp");
-    if (!directory) {
-        fprintf(stderr, "/tmp: opendir: %s\n", strerror(errno));
-    }
-
-    while ((de = readdir(directory)) != NULL) {
-        if (!strncmp(de->d_name, "vlogs.", 5)) {
-            char *path = xasprintf("/tmp/%s", de->d_name);
-            add_target(clients, n_clients, path, ok);
-            free(path);
-        }
-    }
-
-    closedir(directory);
-}
-
 int main(int argc, char *argv[])
 {
     static const struct option long_options[] = {
         /* Target options must come first. */
-        {"all", no_argument, NULL, 'a'},
         {"target", required_argument, NULL, 't'},
         {"help", no_argument, NULL, 'h'},
 
@@ -150,6 +143,7 @@ int main(int argc, char *argv[])
         {"list", no_argument, NULL, 'l'},
         {"set", required_argument, NULL, 's'},
         {"reopen", no_argument, NULL, 'r'},
+        {"execute", required_argument, NULL, 'e'},
         {0, 0, 0, 0},
     };
     char *short_options;
@@ -157,7 +151,7 @@ int main(int argc, char *argv[])
     /* Determine targets. */
     bool ok = true;
     int n_actions = 0;
-    struct vlog_client **clients = NULL;
+    struct unixctl_client **clients = NULL;
     size_t n_clients = 0;
 
     set_program_name(argv[0]);
@@ -172,27 +166,23 @@ int main(int argc, char *argv[])
         if (option == -1) {
             break;
         }
-        if (!strchr("ath", option) && n_clients == 0) {
+        if (!strchr("th", option) && n_clients == 0) {
             ofp_fatal(0, "no targets specified (use --help for help)");
         } else {
             ++n_actions;
         }
         switch (option) {
-        case 'a':
-            add_all_targets(&clients, &n_clients, &ok);
-            break;
-
         case 't':
             add_target(&clients, &n_clients, optarg, &ok);
             break;
 
         case 'l':
             for (i = 0; i < n_clients; i++) {
-                struct vlog_client *client = clients[i];
+                struct unixctl_client *client = clients[i];
                 char *reply;
 
-                printf("%s:\n", vlog_client_target(client));
-                reply = transact(client, "list", &ok);
+                printf("%s:\n", unixctl_client_target(client));
+                reply = transact(client, "vlog/list", &ok);
                 fputs(reply, stdout);
                 free(reply);
             }
@@ -200,8 +190,8 @@ int main(int argc, char *argv[])
 
         case 's':
             for (i = 0; i < n_clients; i++) {
-                struct vlog_client *client = clients[i];
-                char *request = xasprintf("set %s", optarg);
+                struct unixctl_client *client = clients[i];
+                char *request = xasprintf("vlog/set %s", optarg);
                 transact_ack(client, request, &ok);
                 free(request);
             }
@@ -209,13 +199,19 @@ int main(int argc, char *argv[])
 
         case 'r':
             for (i = 0; i < n_clients; i++) {
-                struct vlog_client *client = clients[i];
-                char *request = xstrdup("reopen");
+                struct unixctl_client *client = clients[i];
+                char *request = xstrdup("vlog/reopen");
                 transact_ack(client, request, &ok);
                 free(request);
             }
             break;
 
+        case 'e':
+            for (i = 0; i < n_clients; i++) {
+                execute_command(clients[i], optarg, &ok);
+            }
+            break;
+
         case 'h':
             usage(argv[0], EXIT_SUCCESS);
             break;
index 8050b0e429ebf2631dc02f022b78375f9075892e..5fa0fd974566842e1b5976b9a01e91c5342caf1b 100644 (file)
@@ -49,8 +49,8 @@
 #include "signals.h"
 #include "svec.h"
 #include "timeval.h"
+#include "unixctl.h"
 #include "util.h"
-#include "vlog-socket.h"
 
 #include "vlog.h"
 #define THIS_MODULE VLM_brcompatd
@@ -559,6 +559,7 @@ rtnl_recv_update(void)
 int
 main(int argc, char *argv[])
 {
+    struct unixctl_server *unixctl;
     int retval;
 
     set_program_name(argv[0]);
@@ -574,7 +575,7 @@ main(int argc, char *argv[])
 
     fatal_signal_add_hook(release_lock, NULL, true);
 
-    retval = vlog_server_listen(NULL, NULL);
+    retval = unixctl_server_create(NULL, &unixctl);
     if (retval) {
         ofp_fatal(retval, "could not listen for vlog connections");
     }
@@ -599,6 +600,7 @@ main(int argc, char *argv[])
     cfg_read();
 
     for (;;) {
+        unixctl_server_run(unixctl);
         brc_recv_update();
 
         /* If 'prune_timeout' is non-zero, we actively prune from the
@@ -620,7 +622,7 @@ main(int argc, char *argv[])
         }
 
         nl_sock_wait(brc_sock, POLLIN);
-
+        unixctl_server_wait(unixctl);
         poll_block();
     }
 
index fc2161685e7a992e68ef9ba8481d48cf6c79e35e..e5cedcb9b0c0d2c361b52395026c364cf04870bb 100644 (file)
 #include "signals.h"
 #include "svec.h"
 #include "timeval.h"
+#include "unixctl.h"
 #include "util.h"
 #include "vconn-ssl.h"
 #include "vconn.h"
-#include "vlog-socket.h"
 #include "vswitchd.h"
 
 #include "vlog.h"
@@ -65,6 +65,7 @@ char *config_file;
 int
 main(int argc, char *argv[])
 {
+    struct unixctl_server *unixctl;
     struct signal *sighup;
     bool need_reconfigure;
     int retval;
@@ -81,9 +82,9 @@ main(int argc, char *argv[])
     die_if_already_running();
     daemonize();
 
-    retval = vlog_server_listen(NULL, NULL);
+    retval = unixctl_server_create(NULL, &unixctl);
     if (retval) {
-        ofp_fatal(retval, "could not listen for vlog connections");
+        ofp_fatal(retval, "could not listen for control connections");
     }
 
     cfg_read();
@@ -103,10 +104,12 @@ main(int argc, char *argv[])
         if (bridge_run()) {
             need_reconfigure = true;
         }
+        unixctl_server_run(unixctl);
 
         signal_wait(sighup);
         mgmt_wait();
         bridge_wait();
+        unixctl_server_wait(unixctl);
         poll_block();
     }