[net.bugs.4bsd] Another flock bug report with fix

jpl@allegra.UUCP (John P. Linderman) (02/21/86)

Index:	/sys/sys/sys_inode.c 4.2BSD

Description:
    In the process of looking over Robin's flock fix, I discovered
    another peculiarity in the locking code.  If you try to upgrade a
    shared lock to an exclusive lock without blocking, you will never
    succeed.  I view this as much less serious than Robin's problem
    (multiple processes securing exclusive locks simultaneously), but
    it shouldn't be the way it is.

Repeat-By:
    Run the following.  The nonlocking exclusive upgrade will fail,
    suggesting the process would block, but the blockable upgrade will
    succeed, probably without blocking.

    #include <sys/file.h>
    #include <stdio.h>

    main()
    {
	int fd;
	int rc;

	fd = open("/dev/null", O_RDONLY);
	printf("Got %d opening file\n", fd);
	if (fd == -1) exit(1);
	rc = flock(fd, LOCK_SH | LOCK_NB);
	printf("Shared nonblocking lock returned %d\n", rc);
	if (rc == -1) {
	    perror("shared");
	    exit(1);
	}
	rc = flock(fd, LOCK_EX | LOCK_NB);
	printf("Exclusive nonblocking lock returned %d\n", rc);
	if (rc == -1) {
	    perror("exclusive nonblocking");
	    rc = flock(fd, LOCK_EX);
	    if (rc == -1) perror("exclusive");
	    exit(1);
	}
	exit(0);
    }

Fix:
    The problem is that ino_lock() gives up for non-blocking exclusive
    locks as soon as it sees a shared lock, even if it is our own
    shared lock.  If the shared lock is released (as it must be before
    an exclusive lock can be granted), we may not have to wait.  The
    fix below (which incorporates Robin's changes as well as my own),
    simply moves the check for blocking past the release of any shared
    lock.  This is not an unvarnished win.  With this change, a process
    may lose its shared lock, then fail because it would have blocked
    trying to acquire the exclusive lock.  This seems to me to be
    preferable to the current alternative of retaining the shared lock,
    but always failing, but it would also be possible to do a little
    more snooping (check the shared lock count for example) to at
    least reduce the probability of losing the shared lock without
    getting the exclusive one.  I leave this to the professionals.

*** old sys_inode.c	Fri Feb 21 14:21:32 1986
--- sys_inode.c	Fri Feb 21 13:28:17 1986
***************
*** 401,406
  		/*
  		 * Must wait for any shared locks to finish
  		 * before we try to apply a exclusive lock.
  		 */
  		while (ip->i_flag & ISHLOCK) {
  			if (cmd & LOCK_NB)

--- 401,408 -----
  		/*
  		 * Must wait for any shared locks to finish
  		 * before we try to apply a exclusive lock.
+ 		 * Must also wait if an exclusive lock
+ 		 * was applied during our last wait. (gatech!robin)
  		 */
  		while (ip->i_flag & ISHLOCK) {
  			/*
***************
*** 403,410
  		 * before we try to apply a exclusive lock.
  		 */
  		while (ip->i_flag & ISHLOCK) {
- 			if (cmd & LOCK_NB)
- 				return (EWOULDBLOCK);
  			/*
  			 * If we're holding a shared
  			 * lock, then release it.

--- 405,410 -----
  		 * was applied during our last wait. (gatech!robin)
  		 */
  		while (ip->i_flag & ISHLOCK) {
  			/*
  			 * If we're holding a shared
  			 * lock, then release it.
***************
*** 413,418
  				ino_unlock(fp, FSHLOCK);
  				goto again;
  			}
  			ip->i_flag |= ILWAIT;
  			sleep((caddr_t)&ip->i_shlockc, PLOCK);
  		}

--- 413,420 -----
  				ino_unlock(fp, FSHLOCK);
  				goto again;
  			}
+ 			if (cmd & LOCK_NB)
+ 				return (EWOULDBLOCK);
  			ip->i_flag |= ILWAIT;
  			sleep((caddr_t)&ip->i_shlockc, PLOCK);
  			if (ip->i_flag & IEXLOCK)
***************
*** 415,420
  			}
  			ip->i_flag |= ILWAIT;
  			sleep((caddr_t)&ip->i_shlockc, PLOCK);
  		}
  	}
  	if (fp->f_flag & (FSHLOCK|FEXLOCK))

--- 417,424 -----
  				return (EWOULDBLOCK);
  			ip->i_flag |= ILWAIT;
  			sleep((caddr_t)&ip->i_shlockc, PLOCK);
+ 			if (ip->i_flag & IEXLOCK)
+ 				goto again;
  		}
  	}
  	if (fp->f_flag & (FSHLOCK|FEXLOCK))