Show local_shell.c syntax highlighted
/*
* $Id: local_shell.c 14335 2007-08-06 14:21:26Z cbiere $
*
* Copyright (c) 2006, Christian Biere
*
*----------------------------------------------------------------------
* This file is part of gtk-gnutella.
*
* gtk-gnutella is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* gtk-gnutella is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gtk-gnutella; if not, write to the Free Software
* Foundation, Inc.:
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*----------------------------------------------------------------------
*/
/**
* @ingroup core
* @file
*
* Local shell
*
* This implements an alterego of gtk-gnutella to access the local socket
* of gtk-gnutella.
*
* @author Christian Biere
* @date 2006
*/
/**
* @note This file can also be compiled as a tiny standalone tool
* with no external dependencies (not even GLib):
*
* cc -o gtkg-shell -DLOCAL_SHELL_STANDALONE local_shell.c
*/
#ifdef LOCAL_SHELL_STANDALONE
#include <sys/types.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <pwd.h>
static inline int
is_temporary_error(int e)
{
return EAGAIN == e || EINTR == e;
}
void
socket_set_nonblocking(int fd)
{
int ret, flags;
ret = fcntl(fd, F_GETFL);
flags = ret | O_NONBLOCK;
if (flags != ret)
fcntl(fd, F_SETFL, flags);
}
#else /* !LOCAL_SHELL_STANDALONE */
#include "common.h"
RCSID("$Id: local_shell.c 14335 2007-08-06 14:21:26Z cbiere $");
#include "lib/misc.h"
#include "lib/socket.h"
#include "lib/override.h"
#endif /* LOCAL_SHELL_STANDALONE */
/* Fallback on select() if they miss poll() */
#ifndef HAS_POLL
#ifdef HAS_SELECT
#define USE_SELECT_FOR_SHELL
#endif
#endif
#if defined(__APPLE__) && defined(__MACH__)
/* poll() seems to be broken on Darwin */
#ifndef USE_SELECT_FOR_SHELL
#define USE_SELECT_FOR_SHELL
#endif
#endif /* Darwin */
#ifdef USE_READLINE
#include <readline/readline.h>
#include <readline/history.h>
#endif
struct shell_buf {
char *buf; /**< Arbitrary buffer maybe static/local/dynamic */
size_t size; /**< Amount of bytes that buf can hold */
size_t fill; /**< Amount readable bytes in buf from pos */
size_t pos; /**< Read position in buf */
unsigned eof:1; /**< If set, no further read() possible due to EOF */
unsigned hup:1; /**< If set, no further write() possible due to HUP */
unsigned readable:1; /**< If set, read() should succeed */
unsigned writable:1; /**< If set, write() should succeed */
unsigned shutdown:1; /**< If set, a shutdown has been signalled */
unsigned wrote:1; /**< If set, last call to write() succeeded */
};
struct line_buf {
char *buf; /**< Dynamically allocated; use free() */
size_t length; /**< Length of the string in buf */
size_t pos; /**< Read position relative to buf */
};
/**
* Attempts to fill the shell buffer from the given file descriptor, however,
* the buffer is not further filled before it is completely empty.
*/
static int
read_data(int fd, struct shell_buf *sb)
{
if (!sb) {
return -1;
}
if (0 == sb->fill && sb->readable) {
ssize_t ret;
ret = read(fd, sb->buf, sb->size);
switch (ret) {
case 0:
sb->eof = 1;
break;
case -1:
if (!is_temporary_error(errno)) {
perror("read() failed");
return -1;
}
break;
default:
sb->fill = ret;
}
}
return 0;
}
/**
* Attempts to fill the shell buffer using readline(), however,
* the buffer is not further filled before it is completely empty.
*/
static int
read_data_with_readline(struct line_buf *line, struct shell_buf *sb)
#ifdef USE_READLINE
{
if (!line || !sb) {
return -1;
}
if (0 == sb->fill) {
if (!line->buf) {
errno = 0;
line->buf = readline("");
if (!line->buf && !is_temporary_error(errno)) {
sb->eof = 1;
}
line->length = line->buf ? strlen(line->buf) : 0;
line->pos = 0;
}
if (line->buf) {
if (line->pos < line->length) {
size_t n;
n = line->length - line->pos;
if (n > sb->size) {
n = sb->size;
}
memcpy(sb->buf, &line->buf[line->pos], n);
sb->fill = n;
line->pos += n;
}
if (line->pos == line->length && sb->fill < sb->size) {
sb->buf[sb->fill] = '\n';
sb->fill++;
free(line->buf);
line->buf = NULL;
line->length = 0;
line->pos = 0;
}
}
}
return 0;
}
#else /* !USE_READLINE */
{
(void) line;
(void) sb;
return -1;
}
#endif /* USE_READLINE */
/**
* Attempts to flush the shell buffer to the given file descriptor.
*/
static int
write_data(int fd, struct shell_buf *sb)
{
if (!sb) {
return -1;
}
sb->wrote = 0;
if (sb->fill > 0 && sb->writable) {
ssize_t ret;
ret = write(fd, &sb->buf[sb->pos], sb->fill);
switch (ret) {
case 0:
sb->hup = 1;
break;
case -1:
if (EPIPE == errno) {
sb->hup = 1;
} else if (!is_temporary_error(errno)) {
perror("write() failed");
return -1;
}
break;
default:
sb->fill -= (size_t) ret;
if (sb->fill > 0) {
sb->pos += (size_t) ret;
} else {
sb->pos = 0;
}
sb->wrote = 1;
}
}
return 0;
}
static int
compat_poll(struct pollfd *fds, size_t n, int timeout)
#ifdef USE_SELECT_FOR_SHELL
{
struct timeval tv;
size_t i;
fd_set rfds, wfds, efds;
int ret, max_fd = -1;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000UL;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&efds);
for (i = 0; i < n; i++) {
int fd = fds[i].fd;
if (fd < 0 || fd >= FD_SETSIZE) {
fds[i].revents = POLLERR;
continue;
}
max_fd = MAX(fd, max_fd);
fds[i].revents = 0;
if (POLLIN & fds[i].events) {
FD_SET(fd, &rfds);
}
if (POLLOUT & fds[i].events) {
FD_SET(fd, &wfds);
}
FD_SET(fd, &efds);
}
ret = select(max_fd + 1, &rfds, &wfds, &efds, timeout < 0 ? NULL : &tv);
if (ret > 0) {
for (i = 0; i < n; i++) {
int fd = fds[i].fd;
if (fd < 0 || fd >= FD_SETSIZE) {
continue;
}
if (FD_ISSET(fd, &rfds)) {
fds[i].revents |= POLLIN;
}
if (FD_ISSET(fd, &wfds)) {
fds[i].revents |= POLLOUT;
}
if (FD_ISSET(fd, &efds)) {
fds[i].revents |= POLLERR;
}
}
}
return ret;
}
#else /* !USE_SELECT_FOR_SHELL */
{
return poll(fds, n, timeout);
}
#endif /* USE_SELECT_FOR_SHELL */
/**
* Sleeps until any I/O event happens or the timeout expires.
* @return -1 On error;
* 0 If the timeout expired;
* otherwise the amount of ready pollsets is returned.
*/
static int
wait_for_io(struct pollfd *fds, size_t n, int timeout)
{
int ret;
for (;;) {
ret = compat_poll(fds, n, timeout);
if (ret >= 0) {
break;
}
if (ret < 0 && !is_temporary_error(errno)) {
perror("compat_poll() failed");
return -1;
}
}
return ret;
}
static int
local_shell_mainloop(int fd)
{
static struct shell_buf client, server;
int tty = isatty(STDIN_FILENO);
#ifdef USE_READLINE
int use_readline = tty;
#else
int use_readline = 0;
#endif /* USE_READLINE */
{
static char client_buf[4096], server_buf[4096];
static const char helo[] = "HELO\n";
static const char interactive[] = "HELO\nINTR\n";
server.buf = server_buf;
server.size = sizeof server_buf;
client.buf = client_buf;
client.size = sizeof client_buf;
/*
* Only send the empty INTR command when interactive.
*/
if (tty) {
client.fill = sizeof interactive - 1;
memcpy(client_buf, interactive, client.fill);
} else {
client.fill = sizeof helo - 1;
memcpy(client_buf, helo, client.fill);
}
}
for (;;) {
if (use_readline) {
static struct line_buf line;
if (read_data_with_readline(&line, &client)) {
return -1;
}
} else {
if (read_data(STDIN_FILENO, &client)) {
return -1;
}
}
if (write_data(fd, &client)) {
return -1;
}
if (read_data(fd, &server)) {
return -1;
}
if (write_data(STDOUT_FILENO, &server)) {
return -1;
}
if (server.eof && 0 == server.fill) {
/*
* client.eof is not checked because if server.eof is set,
* we expect that the server has completely closed the
* connection and not merely done a shutdown(fd, SHUT_WR).
* The latter is only done on the client-side. Otherwise,
* the shell would not terminate before another write()
* (which should gain 0 or EPIPE) is attempted.
*/
if (client.fill > 0) {
fprintf(stderr, "Server hung up unexpectedly!\n");
return -1;
}
return 0;
}
if (client.eof && 0 == client.fill) {
if ((server.eof && 0 == server.fill) || client.hup) {
return 0;
}
if (!client.shutdown) {
shutdown(fd, SHUT_WR);
client.shutdown = 1;
}
}
{
struct pollfd fds[3];
int ret;
if (client.eof || client.fill > 0) {
fds[0].fd = -1;
fds[0].events = 0;
} else {
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
}
if ((server.fill > 0 || server.wrote) && !server.hup) {
fds[1].fd = STDOUT_FILENO;
fds[1].events = POLLOUT;
} else {
fds[1].fd = -1;
fds[1].events = 0;
}
if (server.fill > 0 || server.eof) {
fds[2].events = 0;
} else {
fds[2].events = POLLIN;
}
if ((client.fill > 0 || client.wrote) && !client.hup) {
fds[2].events |= POLLOUT;
}
fds[2].fd = fds[2].events ? fd : -1;
ret = wait_for_io(fds, 3, -1);
if (ret < 0) {
return -1;
}
client.readable = 0 != (fds[0].revents & (POLLIN | POLLHUP));
client.writable = 0 != (fds[2].revents & POLLOUT);
server.readable = 0 != (fds[2].revents & (POLLIN | POLLHUP));
server.writable = 0 != (fds[1].revents & POLLOUT);
}
}
return 0;
}
/**
* A simple shell to speak to the local socket of gtk-gnutella. This is
* provided because there is not standard tool that could be used like
* telnet for TCP. This is meant as a stand-alone program and therefore
* does not return calls exit().
*/
void
local_shell(const char *socket_path)
#if defined(HAS_POLL) || defined(HAS_SELECT)
{
struct sockaddr_un addr;
int fd;
if (!socket_path) {
goto failure;
}
if (-1 == fcntl(STDIN_FILENO, F_GETFL)) {
goto failure;
}
if (-1 == fcntl(STDOUT_FILENO, F_GETFL)) {
if (STDOUT_FILENO != open("/dev/null", O_WRONLY))
goto failure;
}
if (-1 == fcntl(STDERR_FILENO, F_GETFL)) {
if (STDERR_FILENO != open("/dev/null", O_WRONLY))
goto failure;
}
{
static const struct sockaddr_un zero_un;
addr = zero_un;
addr.sun_family = AF_LOCAL;
if (strlen(socket_path) >= sizeof addr.sun_path) {
fprintf(stderr, "local_shell(): pathname is too long\n");
goto failure;
}
strncpy(addr.sun_path, socket_path, sizeof addr.sun_path);
}
fd = socket(PF_LOCAL, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket(PF_LOCAL, SOCK_STREAM, 0) failed");
goto failure;
}
if (0 != connect(fd, (const void *) &addr, sizeof addr)) {
perror("local_shell(): connect() failed");
close(fd);
fd = -1;
goto failure;
}
socket_set_nonblocking(fd);
if (0 != local_shell_mainloop(fd))
goto failure;
exit(EXIT_SUCCESS);
failure:
exit(EXIT_FAILURE);
}
#else /* !HAS_POLL && !HAS_SELECT*/
{
fprintf(stderr, "No shell for you!\n");
exit(EXIT_FAILURE);
}
#endif /* HAS_POLL || HAS_SELECT */
#ifdef LOCAL_SHELL_STANDALONE
static char *
path_compose(const char *dir, const char *name)
{
size_t dir_len, name_len;
size_t size;
char *path;
if (!dir || !name) {
return NULL;
}
dir_len = strlen(dir);
name_len = strlen(name);
if (name_len >= ((size_t) -1) - 2 || dir_len >= ((size_t) -1) - name_len) {
return NULL;
}
size = dir_len + name_len + 2;
path = malloc(size);
if (!path) {
perror("malloc");
exit(EXIT_FAILURE);
}
memcpy(path, dir, dir_len);
path[dir_len] = '/';
memcpy(&path[dir_len + 1], name, name_len + 1);
return path;
}
static const char *
get_socket_path(void)
{
const char *cfg_dir;
cfg_dir = getenv("GTK_GNUTELLA_DIR");
if (!cfg_dir) {
const char *home_dir;
home_dir = getenv("HOME");
if (!home_dir) {
const struct passwd *pw;
pw = getpwent();
if (pw) {
home_dir = pw->pw_dir;
}
}
if (!home_dir) {
home_dir = "/";
}
cfg_dir = path_compose(home_dir, "/.gtk-gnutella");
}
if (cfg_dir) {
return path_compose(cfg_dir, "/ipc/socket");
} else {
return NULL;
}
}
int
main(void)
{
const char *path;
path = get_socket_path();
if (!path) {
exit(EXIT_FAILURE);
}
local_shell(path);
return 0;
}
#endif /* LOCAL_SHELL_STANDALONE */
/* vi: set ts=4 sw=4 cindent: */
See more files for this project here