/* ======================================================================== * Copyright 1988-2008 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * * ======================================================================== */ /* * Program: Standalone Mailbox Lock program * * Author: Mark Crispin * Networks and Distributed Computing * Computing & Communications * University of Washington * Administration Building, AG-44 * Seattle, WA 98195 * Internet: MRC@CAC.Washington.EDU * * Date: 8 February 1999 * Last Edited: 3 March 2008 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOCKTIMEOUT 5 /* lock timeout in minutes */ #define LOCKPROTECTION 0664 #ifndef MAXHOSTNAMELEN /* Solaris still sucks */ #define MAXHOSTNAMELEN 256 #endif /* Fatal error * Accepts: Message string * exit code * Returns: code */ int die (char *msg,int code) { syslog (LOG_NOTICE,"(%u) %s",code,msg); write (1,"?",1); /* indicate "impossible" failure */ return code; } int main (int argc,char *argv[]) { int ld,i; int tries = LOCKTIMEOUT * 60 - 1; char *s,*dir,*file,*lock,*hitch,tmp[1024]; size_t dlen,len; struct stat sb,fsb; struct group *grp = getgrnam ("mail"); /* get syslog */ openlog (argv[0],LOG_PID,LOG_MAIL); if (!grp || (grp->gr_gid != getegid ())) return die ("not setgid mail",EX_USAGE); if (argc != 3) return die ("invalid arguments",EX_USAGE); for (s = argv[1]; *s; s++) if (!isdigit (*s)) return die ("invalid fd",EX_USAGE); /* find directory */ if ((*argv[2] != '/') || !(file = strrchr (argv[2],'/')) || !file[1]) return die ("invalid path",EX_USAGE); /* calculate lengths of directory and file */ if (!(dlen = file - argv[2])) dlen = 1; len = strlen (++file); /* make buffers */ dir = (char *) malloc (dlen + 1); lock = (char *) malloc (len + 6); hitch = (char *) malloc (len + 6 + 40 + MAXHOSTNAMELEN); if (!dir || !lock || !hitch) return die ("malloc failure",errno); strncpy (dir,argv[2],dlen); /* connect to desired directory */ dir[dlen] = '\0'; printf ("dir=%s, file=%s\n",dir,file); chdir (dir); /* get device/inode of file descriptor */ if (fstat (atoi (argv[1]),&fsb)) return die ("fstat failure",errno); /* better be a regular file */ if ((fsb.st_mode & S_IFMT) != S_IFREG) return die ("fd not regular file",EX_USAGE); /* now get device/inode of file */ if (lstat (file,&sb)) return die ("lstat failure",errno); /* does it match? */ if ((sb.st_mode & S_IFMT) != S_IFREG) return die ("name not regular file",EX_USAGE); if ((sb.st_dev != fsb.st_dev) || (sb.st_ino != fsb.st_ino)) return die ("fd and name different",EX_USAGE); /* build lock filename */ sprintf (lock,"%s.lock",file); if (!lstat (lock,&sb) && ((sb.st_mode & S_IFMT) != S_IFREG)) return die ("existing lock not regular file",EX_NOPERM); do { /* until OK or out of tries */ if (!stat (lock,&sb) && (time (0) > (sb.st_ctime + LOCKTIMEOUT * 60))) unlink (lock); /* time out lock if enough time has passed */ /* SUN-OS had an NFS * As kludgy as an albatross; * And everywhere that it was installed, * It was a total loss. * -- MRC 9/25/91 */ /* build hitching post file name */ sprintf (hitch,"%s.%lu.%lu.",lock,(unsigned long) time (0), (unsigned long) getpid ()); len = strlen (hitch); /* append local host name */ gethostname (hitch + len,MAXHOSTNAMELEN); /* try to get hitching-post file */ if ((ld = open (hitch,O_WRONLY|O_CREAT|O_EXCL,LOCKPROTECTION)) >= 0) { /* make sure others can break the lock */ chmod (hitch,LOCKPROTECTION); /* get device/inode of hitch file */ if (fstat (ld,&fsb)) return die ("hitch fstat failure",errno); close (ld); /* close the hitching-post */ /* Note: link() may return an error even if it actually succeeded. So we * always check for success via the link count, and ignore the error if * the link count is right. */ /* tie hitching-post to lock */ i = link (hitch,lock) ? errno : 0; /* success if link count now 2 */ if (stat (hitch,&sb) || (sb.st_nlink != 2) || (fsb.st_dev != sb.st_dev) || (fsb.st_ino != sb.st_ino)) { ld = -1; /* failed to hitch */ if (i == EPERM) { /* was it because links not allowed? */ /* Probably a FAT filesystem on Linux. It can't be NFS, so try * creating the lock file directly. */ if ((ld = open (lock,O_WRONLY|O_CREAT|O_EXCL,LOCKPROTECTION)) >= 0) { /* get device/inode of lock file */ if (fstat (ld,&fsb)) return die ("lock fstat failure",errno); close (ld); /* close the file */ } /* give up immediately if protection failure */ else if (errno != EEXIST) tries = 0; } } unlink (hitch); /* flush hitching post */ } /* give up immediately if protection failure */ else if (errno == EACCES) tries = 0; if (ld < 0) { /* lock failed */ if (tries--) sleep (1); /* sleep 1 second and try again */ else { write (1,"-",1); /* hard failure */ return EX_CANTCREAT; } } } while (ld < 0); write (1,"+",1); /* indicate that all is well */ read (0,tmp,1); /* read continue signal from parent */ /* flush the lock file */ if (!stat (lock,&sb) && (fsb.st_dev == sb.st_dev) && (fsb.st_ino == sb.st_ino)) unlink (lock); else syslog (LOG_NOTICE,"lock file %s/%s changed dev/inode",dir,lock); return EX_OK; }