From: paul@uxc.cso.uiuc.edu (Paul Pomes - UofIllinois CSO)
Subject: Re: file locking after forks in sendmail 5.65
Reply-To: Paul-Pomes@uiuc.edu
Organization: University of Illinois at Urbana
Date: Thu, 12 Nov 1992 01:03:11 GMT
Keywords: locking files in 5.65

ajm@seqp4.sequoia.com (A.J.Madison) writes:

>I ran into a problem of recipients complaining of receiving 2 copies of the
>same mail.  Careful examination of the headers and the syslogs led me to
>conclude that if a user had the misfortune of sending mail at the very
>instant the system's sendmail daemon woke up to check the queue, the mail
>would end up getting delivered twice.
>
>After reproducing the problem on a test machine, I realized that locking on
>queue files in the spool directory was not working on our system.  We're
>SVR3.2 on the outside and fault tolerant & proprietary on the inside (which
>leaves the possibility the OS folks blew it).

You've nailed the problem square on the head.  In adapting the original IDA
sendmail to the plethora of platforms around the University, I've seen every
variant of the locking problem.  First there's the shock of discovering that
BSD flock() has differing semantics depending in the vendor.  If the OS is
derived from BSD, locks are inherited across fork().  If the OS comes from
the other side of the fence, they're not.

Non-inheritable flock()s are generally implemented as wrappers around fcntl().
fcntl() locks also require that a file be opened for writing to obtain an
exclusive lock.

For non-inheritable locks my fix has been to release the lock just before
the fork() and to re-lock immediately in the child.  If the lock fails
the child assumes a daemon process got the file during the window of
vulnerability and exits.

I use a replacement flock() routine written by Mark Davies and Andy Linton
(hi!) of the Victoria University of Wellington for those systems that lack
a BSD flock().  It uses either fcntl() or lockf().

From sendall() in deliver.c:


	  case SM_FORK:
		if (e->e_xfp != NULL)
			(void) fflush(e->e_xfp);

#if defined(FCNTL_FLOCK) || defined(LOCKF_FLOCK)
		/*
		** lockf()/fcntl() emulation of flock() breaks down here as
		** locks are not inherited across fork().  release lock so
		** child can re-lock.
		**/
		if (lockfp != NULL)
			(void) flock(fileno(lockfp), LOCK_UN);
#endif /* (FCNTL_FLOCK) || (LOCKF_FLOCK) */
		pid = fork();
		if (pid > 0 && tTd(4, 2))
			printf("sendall: forking (pid = %d)\n", pid);
		if (pid < 0)
		{
			mode = SM_DELIVER;
			break;
		}
		else if (pid > 0)
		{
			/* be sure we leave the temp files to our child */
			e->e_id = e->e_df = NULL;
			if (lockfp != NULL)
				(void) fclose(lockfp);
			return;
		}

		/* double fork to avoid zombies */
		if (fork() > 0)
			exit(EX_OK);

#if defined(FCNTL_FLOCK) || defined(LOCKF_FLOCK) 
		/*
		** re-lock tf file in child (again for flock() as lockf())
		*/
		if (lockfp != NULL && flock(fileno(lockfp), LOCK_EX|LOCK_NB) <0)
		{
			/* someone else got in before us */
			e->e_id = NULL;
			(void) fclose(lockfp);
			finis();
		}
#endif /* (FCNTL_FLOCK) || (LOCKF_FLOCK) */

		/* be sure we are immune from the terminal */
		disconnect(FALSE);

		break;
	}

/pbp
-- 
"A well-schooled electorate, being necessary to the security of a free State,
the right of the People to keep and read Books shall not be infringed."
	-- J. Neil Schulman
Xref: sparky comp.mail.sendmail:2688 comp.mail.uucp:2011