/*
* fwld - firmware loading daemon: catches firmware request events from the
* kernel and serves them.
*
*
* Only kernels from version 2.6.10* on provide the uevent netlink socket,
* needed by this program.
*
* Copyright (C) 2007 Mr. KIKKO (mrkiko.rs@gmail.com)
* This program is an HEAVILY modified version of a sample event listener. Here
* are the copyrights:
* Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
*
* This program 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 version 2 of the License.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <syslog.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/user.h>
#include <asm/types.h>
#include <linux/netlink.h>
/* environment buffer, the kernel's size in lib/kobject_uevent.c should fit in */
#define HOTPLUG_BUFFER_SIZE 1024
#define HOTPLUG_NUM_ENVP 32
#define OBJECT_SIZE 512
/* Some variables that should be accessed within other functions */
int sock; /* socket FD */
char *envp[HOTPLUG_NUM_ENVP];
/* Function prototypes... */
void handle_exit(int e);
int handle_fwrequest(char *scpname);
int main(int argc, char *argv[])
{
/* Variables needed during the entire program... */
struct sockaddr_nl snl; /* netlink socket */
/*
Here we use a sockadr_nl structure since we want to use it to create a
netlink socket.
*/
int retval; /* generic return value holder */
/* buffer to hold environment entries passed to the
script invoked by this program... */
/* environment passed by module */
static char buffer[HOTPLUG_BUFFER_SIZE + OBJECT_SIZE];
static char object[OBJECT_SIZE]; /* char array to contain an object */
//const char *devpath; /* pointer to contain the device path */
const char *action; /* action requested by module */
int i;
char *pos; /* used to access these arrays */
size_t bufpos;
ssize_t buflen; /* used to hold the message length */
/*
Exit if not running as root!
*/
if (getuid() != 0) {
printf("%s(%d): this program needs to run under root privileges!\n",argv[0],getpid());
exit(1);
}
if (argc != 2){
printf("%s(%d): this program needs as it's only argument the complete LF script path!\n",argv[0],getpid());
exit(3);
}
/*
Fill the snl structure with 0x0 characters.
*/
memset(&snl, 0x00, sizeof(struct sockaddr_nl));
/*
snl initialization: here we define the kind of socket we need.
*/
/*
We want to use this socket for netlink communication, so we should specify the right family.
Netlink is used to transfer information between kernel and userspace
processes. It consists of a standard sockets-based interface for
userspace processes and an internal kernel API for kernel modules.
We need to catch firmware request events from there.
*/
snl.nl_family = AF_NETLINK;
/*
A socket should have a unique value in this space.
The pid, they say, doesn't identify a process necessarily.
Since this is the first netlink socket, the instruction:
snl.nl_pid = 0;
is equal to the one we used.
*/
snl.nl_pid = getpid();
/*
Multicast group mask... ?????????
Here we need:
- the CAP_NET_ADMIN
or
- an EUID of 0
We want to listen to the multicast group 1 (1 is a bitmask)
*/
snl.nl_groups = 1;
/* logging: here logging starts... */
openlog(argv[0], LOG_CONS | LOG_PID, LOG_DAEMON);
perror("Log init: "); /* mhmmm.... */
/*
Try to create the socket:
Socket domain: PF_NETLINK;
Socked type: SOCK_DGRAM (equivalent to a raw socket in this case)
Socked protocol: NETLINK_KOBJECT_UEVENT
*/
sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
/* Something wrong happened: we can't open the socket, so we exit */
if (sock == -1) {
syslog(LOG_ERR, "error getting socket, exiting...\n");
exit(1);
}
/* Assign a name to a socket: may be I'll understand it better later... */
retval = bind(sock, (struct sockaddr *) &snl,
sizeof(struct sockaddr_nl));
/*
Something wrong happened: we should close the socket and exit.
The -goto- usage will be removed here soon!
*/
if (retval < 0) {
syslog(LOG_ERR,"socket bind failed, exiting...\n");
handle_exit(19);
}
/*
So now we can go to background!
*/
if (daemon(1,1) != 0) {
syslog(LOG_ERR, "can't become a daemon!");
handle_exit(10);
}
/*
How can I set a nice process title here? setproctitle? mhm...
*/
//setproctitle("FWLD - CATCHING EVENTS!");
syslog(LOG_WARNING, "successfully started and waiting for firmware requests!");
while (1) {
/* OH... FINALLY... we can wait for messages to come in! */
buflen = recv(sock, &buffer, sizeof(buffer), 0);
/* What's happened? */
if (buflen < 0) { /* OOPS! */
syslog(LOG_ERR, "error receiving message");
continue;
}
if ((size_t)buflen > sizeof(buffer)-1) /* Message too long! */
{
buflen = sizeof(buffer)-1; /* make buflen coherent... */
buffer[buflen] = '\0'; /* 0-terminate the string by hand! */
syslog(LOG_ERR, "too long message. Problems with your kernel?");
}
/* save start of payload */
bufpos = strlen(buffer) + 1;
action = buffer;
pos = strchr(buffer, '@');
if (!pos)
continue;
pos[0] = '\0';
//devpath = &pos[1];
/* hotplug events have the environment attached - reconstruct envp[] */
for (i = 0; (bufpos < (size_t)buflen) && (i < HOTPLUG_NUM_ENVP-1); i++) {
int keylen;
char *key;
key = &buffer[bufpos];
keylen = strlen(key);
envp[i] = key;
bufpos += keylen + 1;
}
envp[i] = NULL;
/*
Is the current an "add" action?
If not, we should stop processing JUST NOW!
*/
if (strcmp(action, "add") == 0) /* Ok, we should continue! */
for (i = 0; envp[i] != NULL; i++){
if (strncmp("FIRMWARE", envp[i], 8) == 0) {
/*
Here we should handle the firmware request we had.
So, we call the handle_fwrequest function, which does all the fork
paraphernalia.
*/
syslog(LOG_WARNING, "a request has been catched!");
retval = handle_fwrequest(argv[1]);
if (retval != 0) {
/*
Errors occured during the event handling process.
*/
syslog(LOG_ERR, "event handler exited with error code %d!",retval);
handle_exit(7);
}
}
}
}
}
int handle_fwrequest(char *scpname){
pid_t p; /* process ID of this event handler */
int retmain; /* integer returned to main */
retmain = 0; /* if no errors occurs, all is ok */
p = fork();
switch(p){
case 0: /* this case is seen by the CHILD! */
{
if (execve(scpname, NULL, envp) == -1){
/*
On success execve never returns, but we know it'll not be always a
success.
Here we use a rudimentary, but simple and quite extensible, method
to warn the master process some errors happened.
*/
exit(16);
}
break; /* BUT WE SHOULD NEVER GET THERE */
}
default: /* master... */
{
/*
Some considerations needed here.
- our child is not our anymore: it's image is replaced by
execve. This way, we just can't know if the child is going to
change (or not) it's status.
- may be the child will create other childs: we here launch a
script which probably will not generate other childs, but
aniway we need a consistent way to wait for it.
For this we use the waitpid system call and not the wait()
one as probably suggested by many.
If this is wrong, contact me...
*/
int status;
do {
waitpid(p, &status, 0);
} while(!(WIFEXITED(status) || WIFSIGNALED(status)));
/*
Failures in the event handler or in the external command launched by
it?
*/
if (WEXITSTATUS(status) != 0) {
retmain = status;
}
} /* end of the default case instructions set */
} /* end of the switching mechanism instructions set */
/* all was ok... */
return retmain;
} /* end of the handle_fwrequest function */
void handle_exit(int e){
syslog(LOG_ERR, "handling program termination: closing socket...");
close(sock);
exit(e);
}