--- qemu-start/trunk/qemu-start.c 2006/02/11 16:32:44 292 +++ qemu-start/trunk/qemu-start.c 2008/04/09 19:00:11 314 @@ -30,10 +30,202 @@ #include #include #include +#include +#include + +/* Autogenerated by gengetopt */ +#include "cmdline.h" + +/* This define is present in recent glibc, it is not available on sarge */ +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX 64 +#endif #define QEMU "/usr/bin/qemu" #define USER "qemu" +/* + * This function merges the xauthority data of the current X context + * into the QEMU user's .Xauthority file. + */ +void fix_xauthority(void) { + struct passwd *userdata; + int pfd[2]; + char *xauthfile; + char *display; + char *newdisplay; + char *hostname; +// int status1, status2; + int status,pid; + pid_t cpid1, cpid2; + + if (pipe(pfd)) { + perror("pipe failed"); + exit(1); + } + + cpid1 = fork(); + if (cpid1 < 0) { + perror("first fork failed"); + exit(1); + } + + /* Child: will execute the xauth extract */ + if (cpid1 == 0) { + /* Child may run as original process starter */ + if (setgroups(0, NULL)) { + perror("setgroups failed"); + exit(1); + } + if (setgid(getgid())) { + perror("setgid failed"); + exit(1); + } + if (setuid(getuid())) { + perror("setuid failed"); + exit(1); + } + + /* Write side of pipe corresponds to stdout */ + if (dup2(pfd[1],1) < 0) { + perror("dup2 failed"); + exit(1); + } + if (close(pfd[0])) { + perror("close of pfd[0] failed"); + exit(1); + } + if (close(pfd[1])) { + perror("close of pfd[1] failed"); + exit(1); + } + + userdata = getpwuid(getuid()); + if (!userdata) { + fprintf(stderr, "No user '%s'\n", USER); + exit(1); + } + + + /* Get xauthority file to use */ + if (! (xauthfile = getenv("XAUTHORITY"))) { + if (asprintf(&xauthfile,"%s/.Xauthority",userdata->pw_dir) < 1) { + perror("Constructing xauthority filename failed"); + exit(1); + } + } + + /* Get display */ + if (! (display = getenv("DISPLAY"))) { + perror("No DISPLAY variable found\n"); + exit(1); + } + + /* Substitution: if display starts with `localhost', substitute + `localhost' by `HOSTNAME/unix'. */ + if (strstr(display,"localhost") == display) { + hostname = malloc(HOST_NAME_MAX+1); + if (gethostname(hostname,HOST_NAME_MAX+1)) { + perror("Getting hostname failed"); + exit(1); + } + + if (!(newdisplay = malloc(strlen(hostname) + 5 + strlen(display)-9 + 1))) { + perror("Allocating new display failed"); + exit(1); + } + strcpy(newdisplay,hostname); + strcpy(newdisplay + strlen(hostname),"/unix"); + strcpy(newdisplay + strlen(hostname) + 5, display + 9); + display = newdisplay; + } else if (! (display = strdup(display))) { + perror("strdup DISPLAY failed"); + exit(1); + } + + execl("/usr/bin/X11/xauth","/usr/bin/X11/xauth","-f",xauthfile,"extract","-",display,NULL); + exit(1); + } + + + cpid2 = fork(); + if (cpid2 < 0) { + perror("second fork failed"); + exit(1); + } + + /* Child: will execute the xauth merge */ + if (cpid2 == 0) { + /* Get userid data. */ + userdata = getpwnam(USER); + if (!userdata) { + fprintf(stderr, "No user '%s'\n", USER); + exit(1); + } + + if (setgroups(0, NULL)) { + perror("setgroups failed"); + exit(1); + } + + /* Child may run as qemu user */ + if (setgid(userdata->pw_gid)) { + perror("setgid failed"); + exit(1); + } + if (setuid(userdata->pw_uid)) { + perror("setuid failed"); + exit(1); + } + /* Read side of pipe is stdin */ + if (dup2(pfd[0],0) < 0) { + perror("dup2 failed"); + exit(1); + } + if (close(pfd[0])) { + perror("close of pfd[0] failed"); + exit(1); + } + if (close(pfd[1])) { + perror("close of pfd[1] failed"); + exit(1); + } + + if (asprintf(&xauthfile,"%s/.Xauthority",userdata->pw_dir) < 1) { + perror("Constructing xauthority filename failed"); + exit(1); + } + + execl("/usr/bin/X11/xauth","/usr/bin/X11/xauth","-f",xauthfile,"merge","-",NULL); + exit(1); + } + + if (close(pfd[0])) { + perror("close of pfd[0] failed"); + exit(1); + } + if (close(pfd[1])) { + perror("close of pfd[1] failed"); + exit(1); + } + + while(1) { + + pid = wait(&status); + if ((pid < 1) && (errno == ECHILD)) + break; + if (pid < 1) { + perror("wait failed"); + exit(1); + } + if (! WIFEXITED(status) || WEXITSTATUS(status)) { + fprintf(stderr,"Child returned error\n"); + exit(1); + } + } +} + + /* Tiny code to open tap/tun device, and hand the fd to qemu. Run as root, drops to given user. */ int main(int argc, char *argv[]) @@ -41,36 +233,50 @@ struct ifreq ifr; struct passwd *userdata; char *newargs[argc + 2]; - int i,fd,vlan,tapnr; - char *ptr; - char *command; + int fd,vlan,tapnr; + unsigned int i; + unsigned new_arg_count = 5; + struct gengetopt_args_info args_info; char *display; - char *xauthfile; + char *system; + char *topleft; - /* Check parameters */ - if (argc < 4) { - fprintf(stderr, "Usage: qemu-start TAPDEVNR VLAN SYSTEM ...\n"); - fprintf(stderr, " A device tapTAPDEVNR must have been configured.\n"); - fprintf(stderr, " VLAN is the Virtual LAN number (just use 0 if unsure).\n"); - fprintf(stderr, " SYSTEM is the system to emulate. Use \"\" for the current architecture\n"); - fprintf(stderr, " (uses /usr/bin/qemm); else a binary /usr/bin/qemu-system-SYSTEM is used.\n"); - exit(1); - } + /* Ugly fix because gengetopt 2.18 does not have the `usage' option */ + gengetopt_args_info_usage = "qemu-start [OPTIONS] -- [OPTIONS for QEMU]"; - /* First parameter: TAPDEVNR */ - tapnr = strtol(argv[1],&ptr,0); - if (*ptr) { - fprintf(stderr, "Invalid value for TAPDEVNR parameter (use a number!)\n"); + if (cmdline_parser(argc, argv, &args_info)) exit(1); + + /* parameter: TAPDEVNR */ + tapnr = args_info.tapnr_arg; + + /* parameter: VLAN */ + vlan = args_info.vlan_arg; + + /* parameter: System type */ + if (args_info.system_given) { + if (index(args_info.system_arg,'/')) { + fprintf(stderr, "Invalid value `%s' for system (may not contain a slash character)\n", args_info.system_arg); + exit(1); + } + asprintf(&system, "%s", args_info.system_arg); } - /* Second parameter: VLAN */ - vlan = strtol(argv[2],&ptr,0); - if (*ptr) { - fprintf(stderr, "Invalid value for VLAN parameter (use a number!)\n"); - exit(1); + /* parameters: Window location */ + if (args_info.window_left_given || args_info.window_top_given) { + if (!(args_info.window_left_given && args_info.window_top_given)) { + fprintf(stderr,("If either window-left (x) or window-top (y) are given, then both must be given\n")); + exit(1); + } + if (asprintf(&topleft, "%d,%d",args_info.window_left_arg,args_info.window_top_arg) < 0) { + perror("Preparing top-left environment value string failed"); + exit(1); + } } + /* Export the xauth data */ + fix_xauthority(); + /* Open /dev/net/tun */ fd = open("/dev/net/tun", O_RDWR); if (fd < 0) { @@ -88,50 +294,88 @@ exit(1); } - /* Fix X-display */ - userdata = getpwuid(getuid()); - if (! (xauthfile = getenv("XAUTHORITY"))) - asprintf(&xauthfile,"~%s/.Xauthority",userdata->pw_name); - setuid(0); - setgid(0); - asprintf(&command,"xauth -f %s extract - $(echo $DISPLAY|sed \"s,^localhost,$(hostname)/unix,\") | xauth -f ~%s/.Xauthority merge -; chown %s: ~%s/.Xauthority",xauthfile,USER,USER,USER); -fprintf(stderr,"%s\n",command); - system(command); - - /* Set correct userid. */ + /* Get userid data. */ userdata = getpwnam(USER); if (!userdata) { fprintf(stderr, "No user '%s'\n", USER); exit(1); } - setgroups(0, NULL); - setgid(userdata->pw_gid); - if (setuid(userdata->pw_uid) != 0) { - perror("setting uid"); + + /* Change to QEMU user */ + if (setgroups(0, NULL)) { + perror("setgroups failed"); + exit(1); + } + if (setgid(userdata->pw_gid)) { + perror("setgid failed"); + exit(1); + } + if (setuid(userdata->pw_uid)) { + perror("setuid failed"); exit(1); } /* Clean the environment */ - display=getenv("DISPLAY"); - clearenv(); - setenv("HOME",userdata->pw_dir,1); - setenv("DISPLAY",display,1); - setenv("PATH","/usr/bin:/bin:/usr/local/bin",1); - setenv("LOGNAME",userdata->pw_name,1); - setenv("USER",userdata->pw_name,1); + if(!(display=getenv("DISPLAY"))) { + perror("Getting DISPLAY failed"); + exit(1); + } + if(clearenv()) { + perror("Clearing environment failed"); + exit(1); + } + if(setenv("HOME",userdata->pw_dir,1) || + setenv("DISPLAY",display,1) || + setenv("PATH","/usr/bin:/bin:/usr/local/bin",1) || + setenv("LOGNAME",userdata->pw_name,1) || + setenv("USER",userdata->pw_name,1)) { + perror("Setting environment failed"); + exit(1); + } + if (args_info.window_left_given || args_info.window_top_given) { + if (setenv("SDL_VIDEO_WINDOW_POS", topleft, 1)) { + perror("Setting top-left environment value failed"); + exit(1); + } + } /* Create the right qemu invocation */ - if (!strlen(argv[3])) + if (!args_info.system_given) newargs[0] = QEMU; - else - asprintf(&newargs[0],"%s-system-%s",QEMU,argv[3]); + else if (asprintf(&newargs[0],"%s-system-%s",QEMU,args_info.system_arg) < 0) { + perror("Preparing qemu executable filename failed"); + exit(1); + } newargs[1] = "-net"; - asprintf(&newargs[2],"nic,vlan=%d",vlan); + if (asprintf(&newargs[2],"nic,vlan=%d",vlan) < 0) { + perror("Preparing nic argument failed"); + exit(1); + } + if (args_info.macaddr_given) { + if (asprintf(&newargs[2], "%s,mac=%s", newargs[2], args_info.macaddr_arg) < 0) { + perror("Adding macaddress to nic argument failed"); + } + } + if (args_info.nic_model_given) { + if (asprintf(&newargs[2], "%s,model=%s", newargs[2], args_info.nic_model_arg) < 0) { + perror("Adding nic model to nic argument failed"); + } + } newargs[3] = "-net"; - asprintf(&newargs[4],"tap,fd=%d,vlan=%d",fd,vlan); - for (i = 4; i <= argc; i++) - newargs[i+1] = argv[i]; + if (asprintf(&newargs[4],"tap,fd=%d,vlan=%d",fd,vlan) < 0) { + perror("Preparing tap argument failed"); + exit(1); + } + for (i = 0; i < args_info.inputs_num; i++) { + newargs[i+5] = args_info.inputs[i]; + new_arg_count++; + } + newargs[new_arg_count] = NULL; + + for (i = 0; newargs[i]; i++) + fprintf(stderr, "%d: %s\n", i, newargs[i]); execvp(newargs[0], newargs); + perror("Execution of qemu failed"); exit(1); }