blob: a0b302833bfbacbe1b3688aa27a871640554b48a [file] [log] [blame]
/**
* Copyright © 2016 IBM Corporation
*
* 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.
*/
#define _GNU_SOURCE
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <endian.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "console-server.h"
#define min(a,b) ({ \
const typeof(a) _a = (a); \
const typeof(b) _b = (b); \
_a < _b ? _a : _b; \
})
const size_t buffer_size = 128 * 1024;
struct client {
struct poller *poller;
int fd;
size_t buf_pos;
};
struct socket_handler {
struct handler handler;
struct console *console;
struct poller *poller;
int sd;
uint8_t *buf;
size_t buf_len;
struct client **clients;
int n_clients;
};
static struct socket_handler *to_socket_handler(struct handler *handler)
{
return container_of(handler, struct socket_handler, handler);
}
static void client_close(struct socket_handler *sh, struct client *client)
{
int idx;
close(client->fd);
if (client->poller)
console_unregister_poller(sh->console, client->poller);
for (idx = 0; idx < sh->n_clients; idx++)
if (sh->clients[idx] == client)
break;
assert(idx < sh->n_clients);
free(client);
client = NULL;
sh->n_clients--;
memmove(&sh->clients[idx], &sh->clients[idx+1],
sizeof(*sh->clients) * (sh->n_clients - idx));
sh->clients = realloc(sh->clients,
sizeof(*sh->clients) * sh->n_clients);
}
static size_t client_buffer_len(struct socket_handler *sh,
struct client *client)
{
return sh->buf_len - client->buf_pos;
}
static void *client_buffer_data(struct socket_handler *sh,
struct client *client)
{
return sh->buf + client->buf_pos;
}
static ssize_t send_all(int fd, void *buf, size_t len, bool block)
{
int rc, flags;
size_t pos;
flags = MSG_NOSIGNAL;
if (!block)
flags |= MSG_DONTWAIT;
for (pos = 0; pos < len; pos += rc) {
rc = send(fd, buf + pos, len - pos, flags);
if (rc < 0) {
if (!block && (errno == EAGAIN || errno == EWOULDBLOCK))
break;
if (errno == EINTR)
continue;
return -1;
}
if (rc == 0)
return -1;
}
return pos;
}
/* Drain the queue to the socket and update the queue buffer. If force_len is
* set, send at least that many bytes from the queue, possibly while blocking
*/
static int client_drain_queue(struct socket_handler *sh,
struct client *client, size_t force_len)
{
ssize_t wlen;
size_t len;
bool block;
len = client_buffer_len(sh, client);
if (!len)
return 0;
block = false;
if (force_len) {
assert(force_len <= len);
block = true;
len = force_len;
}
wlen = send_all(client->fd, client_buffer_data(sh, client), len, block);
if (wlen < 0)
return -1;
if (force_len && wlen < force_len)
return -1;
client->buf_pos += wlen;
assert(client->buf_pos <= sh->buf_len);
return 0;
}
static enum poller_ret client_poll(struct handler *handler,
int events, void *data)
{
struct socket_handler *sh = to_socket_handler(handler);
struct client *client = data;
uint8_t buf[4096];
int rc;
if (events & POLLIN) {
rc = recv(client->fd, buf, sizeof(buf), MSG_DONTWAIT);
if (rc < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return POLLER_OK;
else
goto err_close;
}
if (rc == 0)
goto err_close;
console_data_out(sh->console, buf, rc);
}
if (events & POLLOUT) {
rc = client_drain_queue(sh, client, 0);
if (rc)
goto err_close;
}
return POLLER_OK;
err_close:
client->poller = NULL;
client_close(sh, client);
return POLLER_REMOVE;
}
static enum poller_ret socket_poll(struct handler *handler,
int events, void __attribute__((unused)) *data)
{
struct socket_handler *sh = to_socket_handler(handler);
struct client *client;
int fd, n;
if (!(events & POLLIN))
return POLLER_OK;
fd = accept(sh->sd, NULL, NULL);
if (fd < 0)
return POLLER_OK;
client = malloc(sizeof(*client));
memset(client, 0, sizeof(*client));
client->fd = fd;
client->poller = console_register_poller(sh->console, handler,
client_poll, client->fd, POLLIN, client);
n = sh->n_clients++;
sh->clients = realloc(sh->clients,
sizeof(*sh->clients) * sh->n_clients);
sh->clients[n] = client;
return POLLER_OK;
}
static int socket_init(struct handler *handler, struct console *console,
struct config *config __attribute__((unused)))
{
struct socket_handler *sh = to_socket_handler(handler);
struct sockaddr_un addr;
int rc;
sh->console = console;
sh->clients = NULL;
sh->n_clients = 0;
sh->buf_len = 0;
sh->buf = malloc(buffer_size);
if (!sh->buf) {
warn("Can't allocate backlog buffer");
return -1;
}
sh->sd = socket(AF_UNIX, SOCK_STREAM, 0);
if(sh->sd < 0) {
warn("Can't create socket");
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
memcpy(&addr.sun_path, &console_socket_path, console_socket_path_len);
rc = bind(sh->sd, (struct sockaddr *)&addr, sizeof(addr));
if (rc) {
warn("Can't bind to socket path %s",
console_socket_path_readable);
return -1;
}
rc = listen(sh->sd, 1);
if (rc) {
warn("Can't listen for incoming connections");
return -1;
}
sh->poller = console_register_poller(console, handler, socket_poll,
sh->sd, POLLIN, NULL);
return 0;
}
static int socket_data(struct handler *handler, uint8_t *buf, size_t len)
{
struct socket_handler *sh = to_socket_handler(handler);
struct client *client;
size_t space, min_pos;
int i, rc;
space = buffer_size - sh->buf_len;
min_pos = sh->buf_len;
/* Ensure there is at least len bytes available in the global buffer.
*
* The 'space' var tells us how many bytes are available at the tail of
* the buffer. However, if all clients have a non-zero buf_pos, then we
* can drop bytes from the beginning (in the memmove below). So, we
* account for each clients' buf_pos in the space check.
*
* If a client doesn't have sufficient space, perform a blocking write
* to create it. This will result in incrementing client->buf_pos to
* create space.
*/
for (i = 0; i < sh->n_clients; i++) {
ssize_t client_space;
client = sh->clients[i];
client_space = space + client->buf_pos;
if (len > client_space) {
/* Blocking send enough to create len bytes of space in
* the global buffer. On success, this will increment
* client->buf_pos by the number of bytes written
*/
rc = client_drain_queue(sh, client, len - client_space);
if (rc) {
client_close(sh, client);
i--;
continue;
}
}
min_pos = min(min_pos, client->buf_pos);
}
/* avoid pointless copying */
if (!sh->n_clients) {
sh->buf_len = 0;
return 0;
}
/* drop unneeded buffer data... */
sh->buf_len -= min_pos;
memmove(sh->buf, sh->buf + min_pos, sh->buf_len);
/* ... and add new data */
memcpy(sh->buf + sh->buf_len, buf, len);
sh->buf_len += len;
/* now that the queue contains the new data, perform non-blocking send
* to all clients */
for (i = 0; i < sh->n_clients; i++) {
client = sh->clients[i];
/* We've dropped data in the global buffer, so need to update
* clients' pos pointers to suit the new start of buffer
* data */
client->buf_pos -= min_pos;
assert(client->buf_pos >= 0);
rc = client_drain_queue(sh, client, 0);
if (rc) {
client_close(sh, client);
i--;
continue;
}
}
return 0;
}
static void socket_fini(struct handler *handler)
{
struct socket_handler *sh = to_socket_handler(handler);
while (sh->n_clients)
client_close(sh, sh->clients[0]);
if (sh->poller)
console_unregister_poller(sh->console, sh->poller);
close(sh->sd);
free(sh->buf);
}
static struct socket_handler socket_handler = {
.handler = {
.name = "socket",
.init = socket_init,
.data_in = socket_data,
.fini = socket_fini,
},
};
console_register_handler(&socket_handler.handler);