/**
* \file unix_sockets.c
*
* \brief GIS Library - Unix sockets support functions.
*
* Routines related to using UNIX domain sockets for IPC mechanisms
* (such as XDRIVER).
*
* Historically GRASS has used FIFO for interprocess communications for
* display functions. Unfortunately, FIFO's are not available on all
* target platforms. An attempt has been made to use IPC message
* passing, but the semantics are variable and it also isn't available
* on all target platforms. UNIX sockets, or local or domain sockets,
* are much more widely available and consistent.
*
* Note: This implementation of UNIX sockets provides zero
* security checking so should not be used from untrusted clients.
*
* (C) 2001-2008 by the GRASS Development Team
*
* This program is free software under the GNU General Public License
* (>=v2). Read the file COPYING that comes with GRASS for details.
*
* \author Eric G. Miller
*
* \date 1999-2008
*/
#include
#ifdef HAVE_SOCKET
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef __MINGW32__
#define USE_TCP
#include
#include
#define EADDRINUSE WSAEADDRINUSE
#else
#include
#include
#include
#define INVALID_SOCKET (-1)
#endif
#include
#include
#include
/** For systems where the *_LOCAL (POSIX 1g) is not defined
** There's not really any difference between PF and AF in practice.
**/
static char *_get_make_sock_path(void);
static void init_sockets(void)
{
#ifdef __MINGW32__
static int ready;
WSADATA wsadata;
if (ready)
return;
ready = 1;
WSAStartup(0x0001, &wsadata);
#endif
}
/* ---------------------------------------------------------------------
* _get_make_sock_path(), builds and tests the path for the socket
* directory. Returns NULL on any failure, otherwise it returns the
* directory path. The path will be like
* "/tmp/grass7-$USER-$GIS_LOCK".
* ($GIS_LOCK is set in lib/init/init.sh to PID)
* ---------------------------------------------------------------------*/
static char *_get_make_sock_path(void)
{
char *path, *user, *lock;
const char *prefix = "/tmp/grass7";
int len, status;
struct stat theStat;
user = G_whoami(); /* Don't G_free () return value ever! */
if (user == NULL)
return NULL;
else if (user[0] == '?') { /* why's it do that? */
return NULL;
}
if ((lock = getenv("GIS_LOCK")) == NULL)
G_fatal_error(_("Unable to get GIS_LOCK enviroment variable value"));
len = strlen(prefix) + strlen(user) + strlen(lock) + 3;
path = G_malloc(len);
sprintf(path, "%s-%s-%s", prefix, user, lock);
if ((status = G_lstat(path, &theStat)) != 0) {
status = G_mkdir(path);
}
else {
if (!S_ISDIR(theStat.st_mode)) {
status = -1; /* not a directory ?? */
}
else {
status = chmod(path, S_IRWXU); /* fails if we don't own it */
}
}
if (status) { /* something's wrong if non-zero */
G_free(path);
path = NULL;
}
return path;
}
#ifdef USE_TCP
#define PROTO PF_INET
typedef struct sockaddr_in sockaddr_t;
static int set_port(const char *name, int port)
{
FILE *fp = fopen(name, "w");
if (!fp)
return -1;
fprintf(fp, "%d\n", port);
fclose(fp);
return 0;
}
static int get_port(const char *name)
{
FILE *fp = fopen(name, "r");
int port;
if (!fp)
return -1;
if (fscanf(fp, "%d", &port) != 1)
port = -1;
fclose(fp);
return port;
}
static int save_port(int sockfd, const char *name)
{
sockaddr_t addr;
socklen_t size = sizeof(addr);
if (getsockname(sockfd, (struct sockaddr *)&addr, &size) != 0)
return -1;
if (set_port(name, ntohs(addr.sin_port)) < 0)
return -1;
return 0;
}
static int make_address(sockaddr_t * addr, const char *name, int exists)
{
int port = exists ? get_port(name) : 0;
if (port < 0)
return -1;
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr->sin_port = htons((unsigned short)port);
return 0;
}
#else
#define PROTO PF_UNIX
typedef struct sockaddr_un sockaddr_t;
static int make_address(sockaddr_t * addr, const char *name, int exists)
{
addr->sun_family = AF_UNIX;
/* The path to the unix socket must fit in sun_path[] */
if (sizeof(addr->sun_path) < strlen(name) + 1)
return -1;
strncpy(addr->sun_path, name, sizeof(addr->sun_path) - 1);
return 0;
}
#endif
/**
* \brief Builds full path for a UNIX socket.
*
* Caller should G_free() the return value when it is no longer
* needed.
*
* \param[in] name
* \return NULL on error
* \return Pointer to string socket path on success
*/
char *G_sock_get_fname(const char *name)
{
char *path, *dirpath;
int len;
if (name == NULL)
return NULL;
dirpath = _get_make_sock_path();
if (dirpath == NULL)
return NULL;
len = strlen(dirpath) + strlen(name) + 2;
path = G_malloc(len);
sprintf(path, "%s/%s", dirpath, name);
G_free(dirpath);
return path;
}
/**
* \brief Checks socket existence.
*
* \param[in] name
* \return 1 if name exists
* \return 0 if name does not exist
*/
int G_sock_exists(const char *name)
{
struct stat theStat;
if (name == NULL || stat(name, &theStat) != 0)
return 0;
#ifdef USE_TCP
if (S_ISREG(theStat.st_mode))
#else
if (S_ISSOCK(theStat.st_mode))
#endif
return 1;
else
return 0;
}
/**
* \brief Binds socket to file descriptor.
*
* Takes the full pathname for a UNIX socket and returns the file
* descriptor to the socket after a successful call to bind().
*
* \param[in] name
* \return -1 and "errno" is set on error
* \return file descriptor on success
*/
int G_sock_bind(const char *name)
{
int sockfd;
sockaddr_t addr;
socklen_t size;
if (name == NULL)
return -1;
init_sockets();
/* Bind requires that the file does not exist. Force the caller
* to make sure the socket is not in use. The only way to test,
* is a call to connect().
*/
if (G_sock_exists(name)) {
errno = EADDRINUSE;
return -1;
}
/* must always zero socket structure */
memset(&addr, 0, sizeof(addr));
size = sizeof(addr);
if (make_address(&addr, name, 0) < 0)
return -1;
sockfd = socket(PROTO, SOCK_STREAM, 0);
if (sockfd == INVALID_SOCKET)
return -1;
if (bind(sockfd, (const struct sockaddr *)&addr, size) != 0)
return -1;
#ifdef USE_TCP
if (save_port(sockfd, name) < 0)
return -1;
#endif
return sockfd;
}
/**
* \brief Wrapper function to listen().
*
* \param[in] sockfd
* \param[in] queue_len
* \return 0 on success
* \return -1 and "errno" set on error
*/
int G_sock_listen(int sockfd, unsigned int queue_len)
{
return listen(sockfd, queue_len);
}
/**
* \brief Wrapper around accept().
*
* Note: This call will usually block until a connection arrives.
* select() can be used for a time out on the call.
*
* \param[in] sockfd
* \return -1 and "errno" set on error
* \return file descriptor on success
*/
int G_sock_accept(int sockfd)
{
sockaddr_t addr;
socklen_t len = sizeof(addr);
return accept(sockfd, (struct sockaddr *)&addr, &len);
}
/**
* \brief Tries to connect to the UNIX socket specified by name.
*
* \param[in] name
* \return -1 and "errno" set on error
* \return file descriptor on success
*/
int G_sock_connect(const char *name)
{
int sockfd;
sockaddr_t addr;
init_sockets();
if (!G_sock_exists(name))
return -1;
/* must always zero socket structure */
memset(&addr, 0, sizeof(addr));
if (make_address(&addr, name, 1) < 0)
return -1;
sockfd = socket(PROTO, SOCK_STREAM, 0);
if (sockfd == INVALID_SOCKET)
return -1;
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
return -1;
else
return sockfd;
}
#endif