#include #include #include #include #include #include #include #include #include #include #include "misc.h" #include "misc.m" /* #Specification: CONTEXT_LOCK object / principles The CONTEXT_LOCK object provides a way to lock some area in Linuxconf. This is fully multi-user and simple to use. Ideally, every module should provides some locking to prevent two Linuxconf instance from entering at once. The class provides support for both global and fine grain locking. A lock is identified by one or two keys. The first key generally represent the module name or a specific area within linuxconf. The string used should be unique. The module name is a good choice. Using the module name and a suffix is also a good idea, to differentiate various area within one module. The second key may be anything. It may be a constant, but in general the string will be formatted to identify a record. For example, the locking used for a user account could be something like # char tmp[100]; snprintf (tmp,sizeof(tmp),"%s:%s",domain,userid); CONTEXT_LOCK l ("useraccount",tmp); # This provides exclusive access to a user account record in a domain. The lock information is stored in the file /var/run/linuxconf.lockdb. This file contains various information about each lock. This includes the process ID, the user id, the terminal and the display of the process owning the lock. This allows the class to generate enough information so you know who is currently owning the lock (probably you on another console :-) ). The class also supports stale lock (the owner process has gone away without removing the lock). The lock is installed by the constructor and removed by the destructor. */ static CONFIG_FILE f_lockdb ("/var/run/linuxconf.lockdb",help_nil ,CONFIGF_OPTIONAL,"root","root",0600); void context_lock_required(){} struct CONTEXT_LOCK_PRIVATE{ char *key1; char *key2; int flags; bool ok; long fpos; // Position of the lock record in the f_lockdb file }; struct CONTEXT_RECORD { int uid; int pid; time_t stamp; char key1[100]; char key2[100]; char term[20]; char display[200]; }; PRIVATE void CONTEXT_LOCK::init ( const char *_key1, const char *_key2, int _flags) { priv = new CONTEXT_LOCK_PRIVATE; priv->key1 = strdup(_key1); priv->key2 = strdup(_key2); priv->flags = _flags; priv->ok = true; priv->fpos = -1; } PRIVATE void CONTEXT_LOCK::check( bool msg) const // Present a message to the user ? { if (geteuid() == 0){ priv->ok = false; const char *path = f_lockdb.getpath(); // Make sure the file is created int fd = open (path,O_WRONLY|O_CREAT,0600); if (fd != -1){ close (fd); FILE *f = fopen (path,"r+"); if (f != NULL){ fd = fileno (f); if (flock (fd,LOCK_EX)!=-1){ //fprintf (stderr,"Lock ok\n"); priv->ok = true; CONTEXT_RECORD rec; long recfree = -1; while (1){ long t = ftell(f); if (fread (&rec,sizeof(rec),1,f)!=1) break; // fprintf (stderr,"rec %d %d :%s: :%s:\n",rec.uid,rec.pid,rec.key1,rec.key2); if (rec.pid == 0){ // fprintf (stderr,"Free record at %ld\n",t); recfree = t; }else if (strcmp(rec.key1,priv->key1)==0 && strcmp(rec.key2,priv->key2)==0){ // Is the process still alive ? if (kill(rec.pid,0)==-1){ // fprintf (stderr,"Stealing the lock, position %ld\n",t); recfree = t; break; }else{ if (msg){ if (getpid()==rec.pid){ xconf_notice (MSG_U(N_YOURLOCK ,"You are already accessing this area\n" "from Linuxconf. Can't enter")); }else{ const char *user = "root"; struct passwd *p = getpwuid (rec.uid); if (p != NULL) user = p->pw_name; xconf_notice (MSG_U(N_ISLOCKED ,"Another administrator is currently\n" "using Linuxconf in this area\n" "\n" "Here are the details:\n" "Lock granted : %s\n" "User ID : %s\n" "Process ID : %d\n" "Terminal : %s\n" "X display : %s\n") ,asctime(localtime(&rec.stamp)) ,user ,rec.pid,rec.term,rec.display); } } priv->ok = false; break; } } } if (priv->ok){ memset (&rec,0,sizeof(rec)); rec.stamp = time(NULL); rec.uid = getuid(); rec.pid = getpid(); strcpy (rec.key1,priv->key1); strcpy (rec.key2,priv->key2); const char *display = getenv ("DISPLAY"); if (display == NULL) display = ""; const char *term = ttyname (0); if (term == NULL) term = ""; strcpy (rec.term,term); strcpy (rec.display,display); if (recfree != -1) fseek (f,recfree,SEEK_SET); priv->fpos = ftell (f); fwrite (&rec,1,sizeof(rec),f); } flock (fd,LOCK_UN); } fclose (f); } } }else{ priv->ok = true; } } /* Provides exclusive access to an area in Linuxconf. The key is a string identifying an area in Linuxconf. Potentially a module name. */ PUBLIC CONTEXT_LOCK::CONTEXT_LOCK (const char *key) { init (key,"",0); } /* Provides exclusive access to an area in Linuxconf. The first key is a string identifying an area in Linuxconf. Potentially a module name. The second key is identifying a record for example. This constructor is used when you expect more than one administrator to work in some area at once and your module copes with that. If your module loads the whole configuration into memory and write it back, it does not... */ PUBLIC CONTEXT_LOCK::CONTEXT_LOCK (const char *_key1, const char *_key2) { init (_key1,_key2,0); } PUBLIC CONTEXT_LOCK::~CONTEXT_LOCK () { if (priv->fpos != -1){ // We free the lock record FILE *f = fopen (f_lockdb.getpath(),"r+"); if (f != NULL){ int fd = fileno (f); if (flock (fd,LOCK_EX)!=-1){ CONTEXT_RECORD rec; memset (&rec,0,sizeof(rec)); if (fseek (f,priv->fpos,SEEK_SET) != -1){ fwrite (&rec,1,sizeof(rec),f); } flock (fd,LOCK_UN); } fclose (f); } } free (priv->key1); free (priv->key2); delete priv; } /* Return true if the lock was granted. If it returns false, the user has been notified about the lock. You just go away. */ PUBLIC bool CONTEXT_LOCK::isok() const { return isok(0); } /* Wait until the lock is granted. Return true if the lock was granted. If it returns false, the user has been notified about the lock. You just go away. */ PUBLIC bool CONTEXT_LOCK::isok(int seconds) const { bool ret = false; for (; seconds >= 0 && ret == false; seconds--){ check (seconds == 0); ret = priv->ok; if (!ret && seconds > 0) sleep(1); } return ret; }