2008年10月30日 星期四

一個能接收udev event的daemon

//1. gcc -o uevent_receiver -I . uevent_receiver.c
//2. list.h is belong to http://packages.debian.org/lenny/udev
//3. Refer from udevd.c in http://packages.debian.org/lenny/udev
//uevent_receiver.c
#include <&sys/stat.h&>
#include <&sys/wait.h&>
#include <&stdlib.h&>
#include <&unistd.h&>
#include <&errno.h&>
#include <&stdio.h&>
#include <&time.h&>
#include <&fcntl.h&>
#include <&string.h&>
#include <&signal.h&>
#include <&getopt.h&>
#include <&sys/ioctl.h&>
#include <&stdarg.h&>
#include <&sys/syslog.h&>
#include <&sys/socket.h&>
#include <&linux/netlink.h&>
#include "list.h"
#define FD_MAX(a,b) ((a) > (b) ? (a) : (b))
#define READ_END 0
#define WRITE_END 1
#define UEVENT_BUFFER_SIZE 2048
#define UEVENT_NUM_ENVP 32

struct udevd_uevent_msg {
struct list_head node;
pid_t pid;
int exitstatus;
time_t queue_time;
char *action;
char *devpath;
char *subsystem;
char *driver;
dev_t devt;
unsigned long long seqnum;
char *devpath_old;
char *physdevpath;
unsigned int timeout;
char *envp[UEVENT_NUM_ENVP+1];
char envbuf[];
};



static volatile int sigchilds_waiting;
static volatile int udev_exit=0;

static char * get_uevent_msg_str(struct udevd_uevent_msg *msg);
static void reap_sigchilds(void);
static void sig_handler(int signum);


static int init_uevent_netlink_sock(void);
static struct udevd_uevent_msg *get_netlink_msg(void);
static struct udevd_uevent_msg *get_msg_from_envbuf(const char *buf, int buf_size);

static int uevent_netlink_sock = -1;
static int signal_pipe[2] = {-1, -1};


int main(int argc, char *argv[], char *envp[]){

int retval;
int fd;
struct sigaction act;
fd_set readfds;
const char *value;
int daemonize=1;
int option;
int wd,i=0,length;
struct timeval tv;
int rc = 1;
int maxfd;
int op;
static const struct option options[] = {
{ "daemon", 0, NULL, 'd' },
{ "help", 0, NULL, 'h' },
{}
};
if (argc ==1 ){
printf("Usage: uevent_receiver.c [--help] [--daemon]\n");
goto exit;
}

while (1) {
op = getopt_long(argc, argv, "dh", options, NULL);
if (op == -1)
break;

switch (op) {
case 'd':
daemonize = 1;
break;
case 'h':
default:
printf("Usage: udevd [--help] [--daemon]\n");
goto exit;
}
}

openlog("uevent_receiver", LOG_PID, LOG_USER);
syslog(LOG_INFO,"program started.");

if (getuid() != 0) {
syslog(LOG_INFO,"root privileges required");

goto exit;
}

/* make sure std{in,out,err} fd's are in a sane state */
fd = open("/dev/null", O_RDWR);
if (fd < 0) {

syslog(LOG_INFO,"cannot open /dev/null\n");
}
if (fd > STDIN_FILENO)
dup2(fd, STDIN_FILENO);
if (write(STDOUT_FILENO, 0, 0) < 0)
dup2(fd, STDOUT_FILENO);
if (write(STDERR_FILENO, 0, 0) < 0)
dup2(fd, STDERR_FILENO);

if (init_uevent_netlink_sock() < 0) {

syslog(LOG_INFO,"error initializing netlink socket\n");
rc = 3;
goto exit;
}

retval = pipe(signal_pipe);
if (retval < 0) {
syslog(LOG_INFO,"error getting pipes: %s\n", strerror(errno));
goto exit;
}

retval = fcntl(signal_pipe[READ_END], F_GETFL, 0);
if (retval < 0) {
syslog(LOG_INFO,"error fcntl on read pipe: %s\n", strerror(errno));
goto exit;
}
retval = fcntl(signal_pipe[READ_END], F_SETFL, retval | O_NONBLOCK);
if (retval < 0) {
syslog(LOG_INFO,"error fcntl on read pipe: %s\n", strerror(errno));
goto exit;
}

retval = fcntl(signal_pipe[WRITE_END], F_GETFL, 0);
if (retval < 0) {
syslog(LOG_INFO,"error fcntl on write pipe: %s\n", strerror(errno));
goto exit;
}
retval = fcntl(signal_pipe[WRITE_END], F_SETFL, retval | O_NONBLOCK);
if (retval < 0) {
syslog(LOG_INFO,"error fcntl on write pipe: %s\n", strerror(errno));
goto exit;
}
if (daemonize) {
pid_t pid;

pid = fork();
switch (pid) {
case 0:
syslog(LOG_INFO,"daemonized fork running");
break;
case -1:
syslog(LOG_INFO,"fork of daemon failed: %s", strerror(errno));
rc = 4;
goto exit;
default:
syslog(LOG_INFO,"child [%u] running, parent exits", pid);
rc = 0;
goto exit;
}
}

dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO)
close(fd);

chdir("/");
umask(022);
setsid();

/* set signal handlers */
memset(&act, 0x00, sizeof(struct sigaction));
act.sa_handler = (void (*)(int)) sig_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_RESTART;
sigaction(SIGINT, &act, NULL);
sigaction(SIGTERM, &act, NULL);
sigaction(SIGCHLD, &act, NULL);
sigaction(SIGHUP, &act, NULL);

maxfd = uevent_netlink_sock;
maxfd = FD_MAX(maxfd, signal_pipe[READ_END]);
while (!udev_exit) {

struct udevd_uevent_msg *msg;
int fdcount;
FD_ZERO(&readfds);
FD_SET(signal_pipe[READ_END], &readfds);
FD_SET(uevent_netlink_sock, &readfds);

tv.tv_sec = 5;
tv.tv_usec = 0;
fdcount = select(maxfd+1, &readfds, NULL, NULL, NULL);
if (fdcount < 0) {
if (errno != EINTR)
syslog(LOG_INFO,"error in select: %s\n", strerror(errno));
continue;
}

/* get netlink message */
if (FD_ISSET(uevent_netlink_sock, &readfds)) {
msg = get_netlink_msg();
//if (msg)
//msg_queue_insert(msg);
syslog(LOG_INFO,get_uevent_msg_str(msg));
}



/* received a signal, clear our notification pipe */
if (FD_ISSET(signal_pipe[READ_END], &readfds)) {
char buf[256];
read(signal_pipe[READ_END], &buf, sizeof(buf));
}
/* forked child has returned */
if (sigchilds_waiting) {
sigchilds_waiting = 0;
reap_sigchilds();
}
}//End of while loop


rc=0;
exit:

if(uevent_netlink_sock >= 0){
close(uevent_netlink_sock );
}
if(fd >=0)
close(fd);
if (signal_pipe[READ_END] >= 0)
close(signal_pipe[READ_END]);
if (signal_pipe[WRITE_END] >= 0)
close(signal_pipe[WRITE_END]);
closelog();
return rc;
}


static void sig_handler(int signum)
{
switch (signum) {
case SIGINT:
case SIGTERM:
udev_exit=1;
syslog(LOG_INFO,"get SIGTERM.");
break;
case SIGCHLD:
/* set flag, then write to pipe if needed */
sigchilds_waiting = 1;
syslog(LOG_INFO,"get SIGCHLD.");
break;
case SIGHUP:
syslog(LOG_INFO,"get SIGHUP.");
break;
}
/* write to pipe, which will wakeup select() in our mainloop */
write(signal_pipe[WRITE_END], "", 1);

}

static void reap_sigchilds(void)
{
pid_t pid;
int status;

while (1) {
pid = waitpid(-1, &status, WNOHANG);
if (pid <= 0)
break;
if (WIFEXITED(status))
status = WEXITSTATUS(status);
else if (WIFSIGNALED(status))
status = WTERMSIG(status) + 128;
else
status = 0;

}
}
static int init_uevent_netlink_sock(void)
{
struct sockaddr_nl snl;
const int buffersize = 16 * 1024 * 1024;
int retval;

memset(&snl, 0x00, sizeof(struct sockaddr_nl));
snl.nl_family = AF_NETLINK;
snl.nl_pid = getpid();
snl.nl_groups = 1;

uevent_netlink_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
if (uevent_netlink_sock == -1) {
syslog(LOG_INFO,"error getting socket: %s\n", strerror(errno));
return -1;
}

/* set receive buffersize */
setsockopt(uevent_netlink_sock, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));

retval = bind(uevent_netlink_sock, (struct sockaddr *) &snl, sizeof(struct sockaddr_nl));
if (retval < 0) {
syslog(LOG_INFO,"bind failed: %s\n", strerror(errno));
close(uevent_netlink_sock);
uevent_netlink_sock = -1;
return -1;
}
return 0;
}

/* receive the kernel user event message and do some sanity checks */
static struct udevd_uevent_msg *get_netlink_msg(void)
{
struct udevd_uevent_msg *msg;
int bufpos;
ssize_t size;
static char buffer[UEVENT_BUFFER_SIZE+512];
char *pos;

size = recv(uevent_netlink_sock, &buffer, sizeof(buffer), 0);
if (size < 0) {
if (errno != EINTR)
syslog(LOG_INFO,"unable to receive kernel netlink message: %s\n", strerror(errno));
return NULL;
}

if ((size_t)size > sizeof(buffer)-1)
size = sizeof(buffer)-1;
buffer[size] = '\0';

/* start of event payload */
bufpos = strlen(buffer)+1;
msg = get_msg_from_envbuf(&buffer[bufpos], size-bufpos);
if (msg == NULL)
return NULL;

/* validate message */
pos = strchr(buffer, '@');
if (pos == NULL) {
syslog(LOG_INFO,"invalid uevent '%s'\n", buffer);
free(msg);
return NULL;
}
pos[0] = '\0';

if (msg->action == NULL) {
syslog(LOG_INFO,"no ACTION in payload found, skip event '%s'\n", buffer);
free(msg);
return NULL;
}

if (strcmp(msg->action, buffer) != 0) {
syslog(LOG_INFO,"ACTION in payload does not match uevent, skip event '%s'\n", buffer);
free(msg);
return NULL;
}

return msg;
}
static char * get_uevent_msg_str(struct udevd_uevent_msg *msg){

static char buf[1024];
sprintf(buf,"uevent message=>action=%s,devpath=%s,subsystem=%s,timeout=%d",msg->action,msg->devpath,msg->subsystem,msg->timeout);
return buf;
}
static struct udevd_uevent_msg *get_msg_from_envbuf(const char *buf, int buf_size)
{
int bufpos;
int i;
struct udevd_uevent_msg *msg;
char *physdevdriver_key = NULL;
int maj = 0;
int min = 0;

msg = malloc(sizeof(struct udevd_uevent_msg) + buf_size);
if (msg == NULL)
return NULL;
memset(msg, 0x00, sizeof(struct udevd_uevent_msg) + buf_size);

/* copy environment buffer and reconstruct envp */
memcpy(msg->envbuf, buf, buf_size);
bufpos = 0;
for (i = 0; (bufpos < buf_size) && (i < UEVENT_NUM_ENVP-2); i++) {
int keylen;
char *key;

key = &msg->envbuf[bufpos];
keylen = strlen(key);
msg->envp[i] = key;
bufpos += keylen + 1;
//syslog(LOG_INFO,"add '%s' to msg.envp[%i]\n", msg->envp[i], i);

/* remember some keys for further processing */
if (strncmp(key, "ACTION=", 7) == 0)
msg->action = &key[7];
else if (strncmp(key, "DEVPATH=", 8) == 0)
msg->devpath = &key[8];
else if (strncmp(key, "SUBSYSTEM=", 10) == 0)
msg->subsystem = &key[10];
else if (strncmp(key, "DRIVER=", 7) == 0)
msg->driver = &key[7];
else if (strncmp(key, "SEQNUM=", 7) == 0)
msg->seqnum = strtoull(&key[7], NULL, 10);
else if (strncmp(key, "DEVPATH_OLD=", 12) == 0)
msg->devpath_old = &key[12];
else if (strncmp(key, "PHYSDEVPATH=", 12) == 0)
msg->physdevpath = &key[12];
else if (strncmp(key, "PHYSDEVDRIVER=", 14) == 0)
physdevdriver_key = key;
else if (strncmp(key, "MAJOR=", 6) == 0)
maj = strtoull(&key[6], NULL, 10);
else if (strncmp(key, "MINOR=", 6) == 0)
min = strtoull(&key[6], NULL, 10);
else if (strncmp(key, "TIMEOUT=", 8) == 0)
msg->timeout = strtoull(&key[8], NULL, 10);
}
msg->devt = makedev(maj, min);
msg->envp[i++] = "UDEVD_EVENT=1";

if (msg->driver == NULL && msg->physdevpath == NULL && physdevdriver_key != NULL) {
/* for older kernels DRIVER is empty for a bus device, export PHYSDEVDRIVER as DRIVER */
msg->envp[i++] = &physdevdriver_key[7];
msg->driver = &physdevdriver_key[14];
}

msg->envp[i] = NULL;

if (msg->devpath == NULL || msg->action == NULL) {
syslog(LOG_INFO,"DEVPATH or ACTION missing, ignore message\n");
free(msg);
return NULL;
}
return msg;
}

沒有留言: