[net.bugs.4bsd] make runs out of file descriptors

glenn@nsc.UUCP (Glenn Skinner) (07/19/84)

Subject: make runs out of file descriptors
Index:	bin/make/dosys.c 4.2BSD Fix

Description:
	When make executes a command, it vforks, closes open directories,
	and then execs a shell to run the command.  The code for closing
	open directories violates the constraints on what can be done between
	vfork and exec, leaving the parent process in a state where it
	appears that descriptors have been closed when they really haven't.
	When the parent later reexamines these directories, it reopens them,
	piling up extra file descriptors.  Eventually it runs out and
	terminates prematurely.  (See Fix: below for a more complete
	description.)

Repeat-By:
	The only test cases I have are too large to reproduce here.
	Set up a makefile that has to examine several directories and
	that builds many objects ((# of dirs) * (# of objects) > NOFILE).
	Issue a make to rebuild all the objects.

	Alternatively, instrument srchdir() in files.c to print a diagnostic
	whenever it opens a directory, printing out the resulting descriptor.
	Old descriptors won't get reused.

Fix:
	The following diff -c listing contains the cure, as well as
	a more detailed description of the problem.  Line numbers won't
	match, as we've added our own header to the file.

		-- Glenn Skinner
		National Semiconductor, Microprocessor Systems Division
		(408) 733-2600 x 335
		{amd,fortune,ihnp4}!nsc!glenn

*** dosys.c-1.1	Wed Jul 18 16:51:26 1984
--- dosys.c-1.2	Wed Jul 18 15:04:47 1984
***************
*** 82,88
  
  
! doclose()	/* Close open directory files before exec'ing */
  {
  register struct dirhdr *od;
  

--- 82,108 -----
  
  
! /*
!  * Close open directory files before exec'ing.
!  *	Warning!  This routine can be called ONLY by a child
!  *	process after a fork or vfork.  It leaves the directory
!  *	data structures in an inconsistent state.  This is OK for
!  *	the child, since it will exec before any harm can come of
!  *	it, but disastrous for the parent.
!  *
!  *	The routine works this way to avoid another subtle problem.
!  *	If we're to leave things consistent, we must call closedir
!  *	to clean up malloc'ed data structures when we close the
!  *	descriptor for the directory itself.  However, we must then
!  *	change od->dirfc to NULL, since we'd otherwise have a dangling
!  *	pointer.  This will (later) convince the parent that the
!  *	directory has been closed, leading it to reopen the directory.
!  *	Unfortunately, in the parent process, the directory descriptor
!  *	hasn't been closed (the child did it, not the parent), so
!  *	reopening the directory gets us an extra descriptor.  If this
!  *	happens often enough, we run out of descriptors unnecessarily.
!  */
! doclose()
  {
  register struct dirhdr *od;
  
***************
*** 87,96
  register struct dirhdr *od;
  
  for (od = firstod; od; od = od->nxtopendir)
! 	if (od->dirfc != NULL) {
! 		closedir(od->dirfc);
! 		od->dirfc = NULL;
! 	}
  }
  

--- 107,114 -----
  register struct dirhdr *od;
  
  for (od = firstod; od; od = od->nxtopendir)
! 	if (od->dirfc != NULL)
! 		(void) close(od->dirfc->dd_fd);
  }