/* unixechoserver.c - Daemon using Unix sockets. It just echoes back what
it receives. It is an on-demand concurrent server with clients
handled by separate tasks. The command is given a file path parameter
[ "/tmp" by default ]:
Usage:
% unixechoserver []
Assuming that the directory is /tmp and argv[0], the name of the server
image, is unixechoserver:
The name of the file associated with the socket is /tmp/unixechoserver
The process id of the server is in the file /tmp/unixechoserver.pid
The log file is /tmp/unixechoserver.log
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <fcntl.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
extern int errno;
#define PATHMAX 255
static char unixserver_path[PATHMAX+1] = "/tmp"; /* default */
static char unixsocket_path[PATHMAX+1];
static char unixlog_path[PATHMAX+1];
static char unixpid_path[PATHMAX+1];
/* If we are waiting reading from a pipe and
the interlocutor dies abruptly (say because
of ^C or kill -9), then we receive a SIGPIPE
signal. Here we handle that.
*/
void sig_pipe(int n)
{
perror("Broken pipe signal");
}
/* Handler for SIGCHLD signal */
void sig_chld(int n)
{
int status;
fprintf(stderr, "Child terminated\n");
wait(&status); /* So no zombies */
}
/* Initializes the current program as a daemon, by changing working
directory, umask, and eliminating control terminal,
setting signal handlers, saving pid, making sure that only
one daemon is running. Modified from R.Stevens.
*/
void daemon_init(const char * const path, uint mask)
{
pid_t pid;
char buff[256];
static FILE *log; /* for the log */
int fd;
int k;
/* put server in background (with init as parent) */
if ( ( pid = fork() ) < 0 ) {
perror("daemon_init: cannot fork");
exit(0);
} else if (pid > 0) /* The parent */
exit(0);
/* the child */
/* Close all file descriptors that are open */
for (k = getdtablesize()-1; k > 0; k--)
close(k);
/* Redirecting stdin and stdout to /dev/null */
if ( (fd = open("/dev/null", O_RDWR)) < 0) {
perror("Open");
exit(0);
}
dup2(fd, STDIN_FILENO); /* detach stdin */
dup2(fd, STDOUT_FILENO); /* detach stdout */
close (fd);
/* From this point on printf and scanf have no effect */
/* Redirecting stderr to unixlog_path */
log = fopen(unixlog_path, "aw"); /* attach stderr to unixlog_path */
fd = fileno(log); /* obtain file descriptor of the log */
dup2(fd, STDERR_FILENO);
close (fd);
/* From this point on printing to stderr will go to /tmp/mylog */
/* Establish handlers for signals */
if ( signal(SIGCHLD, sig_chld) < 0 ) {
perror("Signal SIGCHLD");
exit(1);
}
if ( signal(SIGPIPE, sig_pipe) < 0 ) {
perror("Signal SIGPIPE");
exit(1);
}
/* Change directory to specified directory */
chdir(path);
/* Set umask to mask (usually 0) */
umask(mask);
/* Detach controlling terminal by becoming sesion leader */
setsid();
/* Put self in a new process group */
pid = getpid();
setpgrp(0, pid);
/* Make sure only one server is running */
if ( ( k = open(unixpid_path, O_RDWR | O_CREAT, 0666) ) < 0 )
exit(1);
if ( lockf(k, F_TLOCK, 0) != 0)
exit(0);
/* Save server's pid without closing file (so lock remains)*/
sprintf(buff, "%6d", pid);
write(k, buff, strlen(buff));
return;
}
/*In an infinite loop, it reads characters from sockfd and writes them
to the same socket
*/
void
chr_echo(int sockfd)
{
for ( ; ; ) {
ssize_t n;
char c;
n = read(sockfd, &c, 1);
if (n > 0) {
write(sockfd, &c, 1);
} else if ( n < 0) {
perror("Negative return from Read");
if (errno == EINTR) /* IO was interrupted by a signal */
continue;
return;
} else /* connection closed by other end */
return;
}
}
/* Create and return a stream listening socket in the Unix
domain using the filepath path.
*/
int unix_listening_socket(const char *path)
{
int fd;
struct sockaddr_un servaddr;
if ( ( fd = socket(AF_UNIX, SOCK_STREAM, 0) ) < 0 ) {
exit(1);
}
bzero((void *)&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_UNIX;
strcpy(servaddr.sun_path, unixsocket_path);
if ( bind(fd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0 ) {
exit(1);
}
if ( listen(fd, 15) < 0 ) {
exit(1);
}
return fd;
}
/* Return a connected socket obtain by an accept call
on the listening socket lsd. It returns -1 if
the accept was interrupted by a signal.
*/
int connected_socket(int lsd)
{
int csd;
size_t clilen;
struct sockaddr_un cliaddr;
clilen = sizeof(cliaddr);
if ( (csd = accept(lsd, (struct sockaddr *) &cliaddr,
(int *)&clilen)) < 0) {
if (errno == EINTR)
return -1;
else {
exit(1);
}
}
return csd;
}
int
main(int argc, char *argv[])
{
int listenfd;
/* Initialize path variables */
if (argc > 1)
strncpy(unixserver_path, argv[1], PATHMAX); /* use argv[1] */
strncat(unixserver_path, "/", PATHMAX-strlen(unixserver_path));
strncat(unixserver_path, argv[0], PATHMAX-strlen(unixserver_path));
strcpy(unixsocket_path, unixserver_path);
strcpy(unixpid_path, unixserver_path);
strncat(unixpid_path, ".pid", PATHMAX-strlen(unixpid_path));
strcpy(unixlog_path, unixserver_path);
strncat(unixlog_path, ".log", PATHMAX-strlen(unixlog_path));
daemon_init(unixserver_path, 0); /* We stay in the unixserver_path directory and file
creation is not restricted. */
unlink(unixsocket_path); /* delete the socket if already existing */
listenfd = unix_listening_socket(unixsocket_path);
for ( ; ; ) {
int connfd;
pid_t childpid;
if ( ( connfd = connected_socket(listenfd) ) < 0 ) continue;
if ( ( childpid = fork() ) == 0) { /* child process */
close(listenfd); /* close listening socket */
chr_echo(connfd); /* process the request */
exit(0); /* terminate child */
} else if (childpid < 0) {
perror("Fork");
exit(1);
}
close(connfd); /* parent closes connected socket */
}
close(listenfd);
return 0;
}