/* This program make sure that only a single instance of another program can be started. With this, we want to avoid the following scenario: New linux user click to start a program. Some large program take a while to start, and most linux desktop do not turn the mouse wait icon. Basically, they start programs and do not care if they actually start. The user is either impatient or the application server is loaded so they click again, and again hoping that this time, the program actually starts. Or the user confuses the panel buttons with desktop accelerator. On the desktop, you double click to initiate an action. On a panel, you click once, since it is a button, not just an icon. The double click is translated in two requests. On a multi-user server, this can lead to a lot of useless tasks starting. In some case (verified with staroffice), starting multiple instance in a short period leads to all failing to start (which is ok) except one: This one do not present any window and use all CPU time. Anyway, you get the picture. This program is used like this startex [ options ] program [ args ... ] startex means starts exclusive. It creates a directory .startex in the HOME directory of the user. startex is not privileged at all. In this directory, it creates symlinks used as a lock mecanism. Symlinks are useful to create a lock file atomically. The value encode in the symlink is the process ID of the startex instance. The name of the symlink is the name of the program. */ #include #include #include #include #include #include #include #include #include #include #include static void usage() { fprintf (stderr,"startex program args\n"); } // Chain to the program and args static int startex_go (char *argv[]) { execvp (argv[0],argv); fprintf (stderr,"startex: execvp %s failed (%s)\n",argv[0],strerror(errno)); return -1; } // Produce a small animation with xeyes static void startex_anim () { pid_t pid = fork(); if (pid == 0){ static char *tb[]={ "xeyes", "-geometry", "40x40+400+300", NULL }; execvp (tb[0],tb); _exit (0); }else if (pid != -1){ sleep (2); kill (pid,SIGTERM); int status; wait (&status); } } static void fsig (int ) { // We must kill our child } static int startex_exec ( const char *prog, char *argv[]) { int ret = -1; // Ok, our lock is in place. We can start the program and wait // until there are no child left pid_t pid = fork(); if (pid == 0){ startex_go (argv); _exit (-1); }else if (pid == -1){ fprintf (stderr,"startex: Can't start %s, can't fork (%s)\n" ,prog,strerror (errno)); }else{ signal (SIGTERM,fsig); startex_anim (); int status; while (wait(&status)!=-1); ret = WEXITSTATUS(status); } return ret; } static int startex_goex (const char *dir, const char *prog, char *argv[]) { int ret = -1; char lock[PATH_MAX]; snprintf (lock,sizeof(lock)-1,"%s/%s.lock",dir,prog); char pid[10]; snprintf (pid,sizeof(pid)-1,"%d",getpid()); for (int i=0; i<2; i++){ if (symlink(pid,lock)==-1){ char oldpid[10]; if (readlink(lock,oldpid,sizeof(oldpid))!=-1){ int oldp = atoi(oldpid); if (kill (oldp,0)==-1){ //stale lock unlink (lock); }else{ // Check if the program has been started for more than // 5 seconds. If this is the case, the user has probably // forgotten about it. The program is running on another // desktop. So we remind the user of this situation with // a small notice window. // The window let him either restart, start another // or simply kills the running process. struct stat st; if (lstat(lock,&st)!=-1){ time_t t = time(NULL); if (t - st.st_mtime > 5){ char command[200]; snprintf (command,sizeof(command)-1 ,"killchoice %s %d",prog,oldp); if (system(command)==0){ unlink (lock); if (symlink(pid,lock)!=-1){ ret = startex_exec (prog,argv); } }else{ ret = 0; } }else{ fprintf (stderr,"startex: %s is already running, bailing out\n" ,prog); ret = 0; } } break; } } }else{ ret = startex_exec (prog,argv); break; } } return ret; } int main (int argc, char *argv[]) { int ret = -1; int i; for (i=1; i