Well, this is something that I happen to know a little bit about.
Opening and closing a serial port with no device connected should not hang. The open()/close() functions do not communicate with the connected device and are always unaware of the presence of something on the other end (the "other end" being the same machine in the case of a loopback adapter).
To avoid read() hangs when no data is being received, you can do something like the following:
1) After opening the serial port device, set it to non-blocking mode. This means that read() will return immediately (with the error EAGAIN) if no bytes were waiting in the buffer to be read, and write() will return immediately (with EAGAIN) if the device was busy and the bytes could not be sent:
Code:
int open_serialport (const char *device) {
int fd, fl;
/* open serial port device */
fd = open(device, O_RDWR | O_NOCTTY);
if (fd == -1)
return -1;
/* get current flags */
fl = fcntl(fd, F_GETFL);
if (fl == -1) {
close(fd);
return -1;
}
/* turn on the nonblocking bit and set the flags */
if (fcntl(fd, F_SETFL, fl | O_NONBLOCK) == -1) {
close(fd);
return -1;
}
return fd;
}
The O_NOCTTY flag prevents the serial port device from becoming the process's terminal device if it doesn't have one already. I've actually never had a problem not specifying this flag but I could conceivable see something weird happen because of it (not related to your problem, though).
Now, I know, you could compress the above code into just a few lines, but I'm trying to keep it readable. You'd also want to add any termios configuration code to that function as well, of course (like setting the baud rate, flow control, linefeed translations, etc). Also don't forget to set CLOCAL in your tcattr c_cflags (see termios man page) if you aren't using a modem; that will cause the drivers to ignore all the modem control lines (although that one might be set by default, can't remember).
Setting nonblocking mode won't solve the problem completely by itself. Although read() won't hang, it is very possible that it will return before the number of bytes you asked for was read. Using select() combined with repeated read() calls will let you control the timeout time and also make sure you receive all the bytes you asked for, while using minimal system resources.
2) Now when you want to read() from the device, you may use select() to wait for data with a timeout, and return if no data was waiting to be read after some amount of time (thus preventing the infinite wait for data to come in). Yes, it adds some extra code, but you do what you have to do (and it's a lot less hacky and way more efficient than forking off child processes to read data):
Code:
int read_serialport (int fd, char *buf, int bytes, int timeout) {
char *dest = buf;
int remaining = bytes, got;
struct timeval tv;
fd_set fdr;
/* while there are bytes left to read... */
while (remaining) {
/* reinit timeval structure each time through loop. */
tv.tv_sec = timeout;
tv.tv_usec = 0;
/* wait for data to come in using select. */
FD_ZERO(&fdr);
FD_SET(fd, &fdr);
if (select(fd + 1, &fdr, NULL, NULL, &tv) != 1) {
/* this could happen on an i/o error too but most likely you timed out waiting
* for incoming data. the way to tell the difference is select() returns -1 on error
* but 0 if the time out expired. */
fprintf(stderr, "timed out!\n");
return -1;
}
/* read a response, we know there's data waiting. */
got = read(fd, dest, remaining);
/* handle errors. retry on EAGAIN, fail on anything else, ignore if no bytes read. */
if (got == -1) {
if (errno == EAGAIN)
continue;
else {
perror("i/o error");
return -1;
}
} else if (got > 0) {
/* figure out how many bytes are left and increment dest pointer through buffer */
dest += got;
remaining -= got;
}
}
/* return number of bytes read, might be 0 if 0 requested. */
return bytes;
}
And you can add stuff to that, like maximum retry counts and more accurate error reporting. You should note that the above code will loop infinitely if read() keeps returning 0 and select() returns immediately every time (adding max retry counts will stop that). That case shouldn't happen but you never can be too safe, some serial port drivers are less than reliable when it comes to that kind of stuff.
The write() logic is similar to the above. However, write() is probably not going to timeout, so you don't necessarily need to use select() to wait for the descriptor to be available for writing; you can just get away with writing bytes and incrementing through the source buffer as above.
With regards to "can I check to see if there's a connection available"... not directly, I don't think. If your device sets one of the other lines high when it is connected (one of those modem lines, like CTS and crap, I don't know much about those) then you should be able to detect if the line is high or not; but chances are your device isn't like that.
However, assuming that your connected device gives some responses to the data you send to it, checking for it's presence is as simple as sending some data to it and seeing if you receive what you expect back from it without timing out. For example, with your loop back connector, all you have to do is send a few bytes. If you don't receive them back, then the connector was not attached. For something like say, a modem, or some other device, you could send some harmless command (like a status command or something) and make sure you get a valid response in a reasonable amount of time. More of a "You there? Yeah I'm here" approach than a "I know you're out there" approach.
Hope that helps,
Jason