[comp.unix.questions] Turning FNDELAY *off*

schaefer@ogccse.ogc.edu (Barton E. Schaefer) (08/11/89)

Surely there must be an obvious way to do this, and I'm just missing it.
I'm using DYNIX 3.0.12 on a Sequent Symmetry, but my question applies
to 4.3 BSD as well, as far as I can tell.  I've asked a guru here and
come up empty.

I have a small program which creates a socket [via socket(2)], binds a
name to it [bind(2)], and prepares to receive connections [listen(2)].
I won't go into the details of these calls because they work as expected.
(Miraculous, eh?)  The program then needs to proceed with another task,
checking the socket periodically for connections from a slave program.
The master program cannot block when checking for these connections.

According to the man page for accept(2):

     The call returns -1 on error.  If it succeeds it returns a
     non-negative integer which is a descriptor for the accepted
     socket.

     The accept will fail if:

     [EWOULDBLOCK]       The socket is marked non-blocking and no
                         connections are present to be accepted.

I therefore make the following call, where sd is the descriptor returned
by socket(2):

    if (fcntl(sd, F_SETFL, FNDELAY) < 0) {
	perror("fcntl");
	exit(1);
    }

I note in passing that FNDELAY is defined only for ttys and sockets, but
a socket is what I have, so all should be well.  I don't get an error
return from fcntl(), in any case.  The program then goes about its
business, periodically doing:

    if ((ad = accept(sd, sd_name, sd_size)) < 0) {
	if (errno != EWOULDBLOCK) {
	    perror("accept");
	    exit(1);
	}
    }
    else {
	/* talk to slave via ad */
	/* much detail omitted */
	if (close(ad) < 0) {
	    perror("close");
	    exit(1);
	}
    }

This works fine and I am able to connect to the slave through the new
descriptor ad.

The problem begins when the slave sometimes cannot read data from the
socket as quickly as the master tries to write it.  The new descriptor
ad inherits the FNDELAY attribute from sd, so the master gets an error
from write(2) when the socket fills up.

So, what I would like to do is turn *off* the FNDELAY attribute of the
new descriptor ad and allow the master to do blocking writes.  I don't
care if this also affects sd, because there is never more than one
master-slave connection at a time and I can reset FNDELAY on sd after
closing the connection on ad.  So the question is

    Is there any way to restore blocking read/write on a descriptor?

I am aware that:

     It is possible to select(2) a socket for the purposes of
     doing an accept by selecting it for read.

so it is not strictly necessary to do the fcntl() in the first place,
and I will convert to use select(2) if necessary.  However, it seems
odd to me that FNDELAY is a permanent condition.  My present interim
solution is to note when the error from write() is EWOULDBLOCK, and in
that case go to sleep for second and then try the write again.  This
seems to lose data on rare, nonrepeatable occasions, so a different
solution is required.

Thanks in advance.
-- 
Bart Schaefer           "And if you believe that, you'll believe anything."
                                                            -- DangerMouse
CSNET / Internet                schaefer@cse.ogc.edu
UUCP                            ...{sequent,tektronix,verdix}!ogccse!schaefer

schaefer@ogccse.ogc.edu (Barton E. Schaefer) (08/13/89)

In article <4180@ogccse.ogc.edu> I wrote:
} Surely there must be an obvious way to do this, and I'm just missing it.
} 
}     if (fcntl(sd, F_SETFL, FNDELAY) < 0) {
} 	perror("fcntl");
} 	exit(1);
}     }
} 
} Is there any way to restore blocking read/write on a descriptor?

As expected, I was indeed missing the obvious.  The following people all
pointed it out in basically the same way, with various degrees of "Hint!
Hint!" :-) commentary leading up to the actual solution:

	Chris Torek <chris@mimsy.umd.edu>
	loverso@Xylogics.COM (John Robert LoVerso)
	guy@auspex.com (Guy Harris)

The answer is that, as I should have realized, fcntl() works just like
ioctl(), and with F_SETFL it sets/clears whatever bits are on/off in the
third parameter.  So for my particular problem, it suffices to do

    if (fcntl(ad, F_SETFL, 0) < 0) {
	perror("fcntl");
    }

and, in the more general case,

    if ((flags = fcntl(ad, F_GETFL, 0)) < 0) {
	perror("fcntl: can't get flags:");
    }
    else if (fcntl(ad, F_SETFL, flags & ~FNDELAY) < 0) {
	perror("fcntl: can't set flags:");
    }

I got a different answer from Phil Hochstetler at Sequent, for you other
DYNIX users.  I haven't tried this one, because the fcntl() solution
works fine.

} From: sequent!phil (Phil Hochstetler)
} 
} I believe the proper way to do this is:
} 
} 	int	on = 1,
} 		off = 0;
} 
} 	/* turn on non blocking I/O on a socket */
} 	if (ioctl(fd, FIONBIO, &on) < 0) {
} 		perror("ioctl");
} 		exit(1);
} 	}
} 
} 	/* turn off non blocking I/O on a socket */
} 	if (ioctl(fd, FIONBIO, &off) < 0) {
} 		perror("ioctl");
} 		exit(1);
} 	}
} 
} I think the problem is a basic BSD one .. 4.X BSD never did this
} the way the doc states (or used to do it both ways).  Give the above
} a try.
} -- 
} Phil Hochstetler		UUCP:  uunet!sequent!phil

Thanks to everyone who responded.
-- 
Bart Schaefer           "And if you believe that, you'll believe anything."
                                                            -- DangerMouse
CSNET / Internet                schaefer@cse.ogc.edu
UUCP                            ...{sequent,tektronix,verdix}!ogccse!schaefer