Current section
Files
Jump to
Current section
Files
c_src/alcove.c
/* Copyright (c) 2014-2018, Michael Santos <michael.santos@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "alcove.h"
#include <sys/stat.h>
static int alcove_signal_init();
static int alcove_rlimit_init();
static int alcove_fd_init(char *fifo);
static int alcove_fdmove(int fd, int dst);
static void usage(void);
extern char *__progname;
int
main(int argc, char *argv[])
{
alcove_state_t *ap = NULL;
int ch = 0;
char *fifo = NULL;
int boot = 1;
struct rlimit maxfd = {0};
#ifndef HAVE_SETPROCTITLE
spt_init(argc, argv);
#endif
ap = calloc(1, sizeof(alcove_state_t));
if (ap == NULL)
exit(ENOMEM);
ALCOVE_SETOPT(ap, alcove_opt_termsig, 1);
ALCOVE_SETOPT(ap, alcove_opt_exit_status, 1);
if (getrlimit(RLIMIT_NOFILE, &maxfd) < 0)
exit(errno);
ap->maxfd = maxfd.rlim_cur;
ap->fdsetsize = ALCOVE_MAXCHILD(ap->maxfd);
ap->maxforkdepth = MAXFORKDEPTH;
ap->signaloneof = SIGTERM;
while ( (ch = getopt(argc, argv, "c:d:h")) != -1) {
switch (ch) {
case 'c':
if (fifo) free(fifo);
fifo = strdup(optarg);
if (fifo == NULL)
exit(ENOMEM);
break;
case 'd':
boot = 0;
ap->depth = (u_int16_t)atoi(optarg);
if (ap->depth > UINT8_MAX)
exit(EAGAIN);
break;
case 'h':
default:
usage();
}
}
ap->child = calloc(ap->fdsetsize, sizeof(alcove_child_t));
if (ap->child == NULL)
exit(ENOMEM);
if (boot) {
if (alcove_signal_init() < 0)
exit(errno);
if (alcove_rlimit_init() < 0)
exit(errno);
if (alcove_fd_init(fifo) < 0)
exit(errno);
}
free(fifo);
alcove_event_init(ap);
exit(0);
}
static int
alcove_signal_init()
{
struct sigaction act = {0};
int sig = 0;
act.sa_handler = SIG_DFL;
(void)sigfillset(&act.sa_mask);
for (sig = 1; sig < NSIG; sig++) {
if (sigaction(sig, &act, NULL) < 0) {
if (errno == EINVAL)
continue;
return -1;
}
}
act.sa_flags |= SA_SIGINFO;
act.sa_sigaction = alcove_sig_info;
if (sigaction(SIGCHLD, &act, NULL) < 0)
return -1;
return 0;
}
static int
alcove_rlimit_init()
{
struct rlimit stack_size = {0};
if (getrlimit(RLIMIT_STACK, &stack_size) < 0)
return -1;
/* Reset an unlimited stack size to a default value. The default is
* set to 8Mb, the default for linux (_STK_LIM in linux/resources.h).
*
* The current value of RLIMIT_STACK is used for allocating the
* stack of cloned processes.
*
*/
if (stack_size.rlim_cur == RLIM_INFINITY) {
stack_size.rlim_cur = 8 * 1024 * 1024;
if (setrlimit(RLIMIT_STACK, &stack_size) < 0)
return -1;
}
return 0;
}
static int
alcove_fd_init(char *fifo)
{
int sigpipe[2] = {0};
int fdctl = 0;
if (!fifo)
return -1;
/* The fd may have been opened by another program. For example,
* valgrind will use the first available fd for the log file. */
if (alcove_fdmove(ALCOVE_SIGREAD_FILENO, 8) < 0)
return -1;
if (alcove_fdmove(ALCOVE_SIGWRITE_FILENO, 8) < 0)
return -1;
if (alcove_fdmove(ALCOVE_FDCTL_FILENO, 8) < 0)
return -1;
if (pipe(sigpipe) < 0)
return -1;
/* XXX fd's will overlap */
if (sigpipe[0] != ALCOVE_SIGREAD_FILENO) {
if (dup2(sigpipe[0], ALCOVE_SIGREAD_FILENO) < 0)
return -1;
if (close(sigpipe[0]) < 0)
return -1;
}
if (sigpipe[1] != ALCOVE_SIGWRITE_FILENO) {
if (dup2(sigpipe[1], ALCOVE_SIGWRITE_FILENO) < 0)
return -1;
if (close(sigpipe[1]) < 0)
return -1;
}
if ( (alcove_setfd(ALCOVE_SIGREAD_FILENO, FD_CLOEXEC|O_NONBLOCK) < 0)
|| (alcove_setfd(ALCOVE_SIGWRITE_FILENO, FD_CLOEXEC|O_NONBLOCK) < 0))
return -1;
/* The control fd used to signal that the port has called exec(). The
* control fd is a fifo. beam opens the fd in read-only mode. When all
* the writers of a fifo call close(), the reader receives EOF.
*
* If the fifo is opened O_WRONLY, the port will deadlock: the open
* call in the port will block for a reader and the erlang side will
* be blocked waiting for the port.
*
* Some OS'es (e.g., Linux, see fifo(7)) allow a fifo to be opened
* in non-blocking mode but the behaviour is unspecified. In practice:
*
* - opening the fifo read-only returns immediately
*
* - opening the fifo write-only fifo returns ENXIO
*
* - opening the fifo read-write with or without O_NONBLOCK will
* return immediately but the control fd cannot be used for sending
* data because each of the readers will race to receive any writes
*
*/
if (mkfifo(fifo, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH) < 0)
return -1;
fdctl = open(fifo, O_RDWR|O_CLOEXEC|O_NONBLOCK);
if (fdctl < 0)
return -1;
if (fdctl != ALCOVE_FDCTL_FILENO) {
if (dup2(fdctl, ALCOVE_FDCTL_FILENO) < 0)
return -1;
if (close(fdctl) < 0)
return -1;
}
return 0;
}
static int
alcove_fdmove(int fd, int dst)
{
int flags = 0;
flags = fcntl(fd, F_GETFD);
if (flags < 0)
return 0;
if (fcntl(fd, F_DUPFD, dst) < 0)
return -1;
/* According to fcntl(2) on FreeBSD, the close-on-exec flag is reset
* by F_DUPFD:
*
* The close-on-exec flag FD_CLOEXEC associated
* with the new file descriptor is cleared, so the
* file descriptor is to remain open across
* execve(2) system calls.
*
* Restore the flag if it was set:
*
*/
return fcntl(dst, F_SETFD, flags);
}
static void
usage()
{
(void)fprintf(stderr, "%s %s\n",
__progname, ALCOVE_VERSION);
(void)fprintf(stderr,
"usage: %s -c <path> [<options>]\n",
__progname
);
exit (ENOTSUP);
}