• Welcome to Valhalla Legends Archive.
 

select() on stdin

Started by iago, November 16, 2005, 08:45 PM

Previous topic - Next topic

iago

Note: has to be compatible with Linux/Solaris.

I'm implementing a server from school, and I need to wait for socket input as well as keyboard input.  I tried the code:

FD_SET(stdin, &my_select_set);
......
select(...)


But if I do that, it errors out while compiling (on the FD_SET line):
iago@slayer:~/project$ make
gcc  -Wall -ansi -std=c89 -g   -c -o server.o server.c
server.c: In function `do_select':
server.c:451: error: invalid operands to binary /
make: *** [server.o] Error 1


I tried with:
FD_SET(0, &my_select_set);
......
select(..)

And it works fine.  So now I'm just doing
#define STDIN 0

At the top, and using that value.  My question is, is there a better way?  Why can't I just pass stdio into FD_SET?  This feels like a horrible workaround :-/

Thanks!
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


Kp

stdin is a FILE*, not an int.  If you really want to make it portable, you could ask for fileno(stdin).  Though, unless the program is doing strange things to redirect stdin to some other descriptor, that'll almost certainly give 0 every time.
[19:20:23] (BotNet) <[vL]Kp> Any idiot can make a bot with CSB, and many do!

iago

Ah, ok!  I am actually using fileno for something else, and I hadn't even thought of that :)

But that reminds me of another problem I'm having.  I can't seem to get rid of this warning:

Quoteserver.c: In function `do_select':
server.c:446: warning: implicit declaration of function `fileno'

According to the man-page, all I have to include for fileno() is <stdio.h>, which I am including.  Any idea how to help it find the function definition?  Or should I just provide it myself? :)
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


iago

Ok, nevermind that. 

I'm actually trying to do this with curses/ncurses now.  But I don't know the proper way, and I can't seem to find any reference. 

I'm making a chat-style program. I have two windows, one for displaying events and one for people to type in their messages.  I have a socket for incoming data, and I have the ncurse input. 

I'm trying to figure out how to read the normal socket data (like, incoming chat messages) while waiting for ncurses input.  I can't find out how to do it with select(), and I've tried doing it with fork() with no success. 

Is there some trick to doing it?  Nothing I could think of turned up any results on Google. :(

Even if I could make one or the other generate a signal, that would be helpful.  Either when data arrives, or when the user hits enter. 
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


iago

Ok, I found some code that does the trick.  I actually borrowed it from a project written by good ol' Tmp :)

I use wgetch() to read in characters, and when doing that I can wait on stdin. 
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


mynameistmp

You'd think this would be a very common situation. I had 0 luck finding anything that could help me. I wound up conjuring up my own way (the way I did it in slackchat). I'm curious as to what sorts of ideas or constructive criticisms you (or anybody else) might have on the subject.

I also wonder if we're reinventing the wheel. How does everyone else deal with this ?
"This idea is so odd, it is hard to know where to begin in challenging it." - Martin Barker, British scholar

iago

Quote from: mynameistmp on November 19, 2005, 04:42 AM
You'd think this would be a very common situation. I had 0 luck finding anything that could help me. I wound up conjuring up my own way (the way I did it in slackchat). I'm curious as to what sorts of ideas or constructive criticisms you (or anybody else) might have on the subject.

I also wonder if we're reinventing the wheel. How does everyone else deal with this ?

Well, you're doing it almost the way I imagined.  The only difference is, I was trying to read a full string.  But that's not terribly realistic, because how would it know where to echo the characters to?

And you're right, you'd think it was more common, but apparently not.  I like your way, though.  I re-coded it my way, but yours provided inspiration :)
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


mynameistmp

How did you deal with things like the input going past the width of the terminal ?

How did you deal with the cursor returning to the right spot in the command line after you printed to your event window (it seems as though you're using windowing (wgetch)) ?

Also I'd be quite interested in how you actually printed to your event window. Did you wind up using scroll() or did you actually have to implement a way to scroll the screen up ? This is complicated when event messages are longer than the width of the terminal (line wrap), because now certain lines have to scroll by more than one.

Are you allowed to post your code ?
"This idea is so odd, it is hard to know where to begin in challenging it." - Martin Barker, British scholar

iago

Quote from: mynameistmp on November 19, 2005, 03:09 PM
How did you deal with things like the input going past the width of the terminal ?
I'm not.  That's a tricky problem, though, and I haven't put much thought into it yet :)

Quote from: mynameistmp on November 19, 2005, 03:09 PM
How did you deal with the cursor returning to the right spot in the command line after you printed to your event window (it seems as though you're using windowing (wgetch)) ?
/* Put the cursor back at the end of the input.  This isn't required, but it looks
* nicer for the user.  Also, clear everything after the cursor.  */
static void reset_cursor()
{
mvwprintw(input_inner, 0, 0, "%s", read_buffer);
/* Note: have to keep this wmove for the cases where read_buffer is the wrong length
* (happens when they press enter */
wmove(input_inner, 0, read_location);
wclrtoeol(input_inner);
wrefresh(input_inner);
}


Quote from: mynameistmp on November 19, 2005, 03:09 PM
Also I'd be quite interested in how you actually printed to your event window. Did you wind up using scroll() or did you actually have to implement a way to scroll the screen up ? This is complicated when event messages are longer than the width of the terminal (line wrap), because now certain lines have to scroll by more than one.
I'm using 4 windows.  2 for the chat area, and 2 for the input area.  The first one in each set is for the border, and the second one is for the actual data.  Here is the code I use for adding it to the window:
/* A recoverable error or debug message has occured.  Display the message, and go on
* with our lives */
void display_message(error_code_t level, char *message, ...)
{
char error_message[MAX_MESSAGE];
va_list ap;
time_t time_val;
char *time_str;

if(level > ERROR_EMERGENCY)
level = ERROR_EMERGENCY;

/* Put the text into a string */
va_start(ap, message);
vsnprintf(error_message, MAX_MESSAGE - 1, message, ap);
error_message[MAX_MESSAGE - 1] = '\0';
va_end(ap);

/* Get the time and strip the endline */
time(&time_val);
time_str = ctime(&time_val);
time_str[strlen(time_str) - 1] = '\0';

wattron(chat_inner, COLOR_PAIR(ERROR_NONE));
wprintw(chat_inner, "[%s] ", time_str);
wattroff(chat_inner, COLOR_PAIR(ERROR_NONE));

wattron(chat_inner, COLOR_PAIR(level));
if(level == ERROR_NONE)
wprintw(chat_inner, "%s", error_message);
else
wprintw(chat_inner, "[%s] %s", error_levels[level], error_message);
wattroff(chat_inner, COLOR_PAIR(level));

wrefresh(chat_inner);
reset_cursor();
}


And the windows are set up like this:
/* The outer chat window is just for the border */
chat_outer = newwin(LINES - 4, COLS, 0, 0);
wborder(chat_outer, 0, 0, 0, 0, 0, 0, 0, 0);
wrefresh(chat_outer);

/* The inner chat window is where everything will be displayed, and is
* scrollable. */
chat_inner = newwin(LINES - 6, COLS - 2, 1, 1);
scrollok(chat_inner, TRUE);

/* This provides the border for the chat window */
input_outer = newwin(3, COLS, LINES - 4, 0);
wborder(input_outer, 0, 0, 0, 0, 0, 0, 0, 0);
wrefresh(input_outer);

/* This is where the user actually gets to type their input */
input_inner = newwin(1, COLS - 2, LINES - 3, 1);
wrefresh(input_inner);

/* Move the cursor to the proper place */
wmove(input_inner, 0, 0);
/* I'm pretty sure this isn't necessary, but make sure it's clear */
wclrtoeol(input_inner);

read_location = 0;
read_buffer[0] = '\0';


Quote from: mynameistmp on November 19, 2005, 03:09 PM
Are you allowed to post your code ?
I don't know, so here it is:
http://www.javaop.com/~iago/output.c
Disclaimer: there's still a lot of code in there from before I was using ncurses, so just ignore it.

I'll post the full project after the due date.  Most people are doing it in Java anyways, and I doubt anybody else is using C, never mind curses.  Here is a screenshot of it, though:
http://www.javaop.com/~iago/nc.png
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


mynameistmp

Quote
I'm not.  That's a tricky problem, though, and I haven't put much thought into it yet

It's actually not too bad. You're going to have to implement arrow keys and backspace and inserting characters into your cl buffer though, which is annoying.
I just setup something like this for the >termwidth input:


if (cmdbufpos > (COLS)) {
clrtoeol();
lline = 1;
printw("%s", inbuf + COLS);
} else {
printw("%s", inbuf);
}


Quote
I'm using 4 windows.  2 for the chat area, and 2 for the input area.  The first one in each set is for the border, and the second one is for the actual data.  Here is the code I use for adding it to the window:

You'll have a much easier time with this than I did. I didn't use windows so I couldn't set a scrollable attribute. I had to implement that myself.
"This idea is so odd, it is hard to know where to begin in challenging it." - Martin Barker, British scholar

iago

Quote from: mynameistmp on November 19, 2005, 05:40 PM
Quote
I'm not.  That's a tricky problem, though, and I haven't put much thought into it yet

It's actually not too bad. You're going to have to implement arrow keys and backspace and inserting characters into your cl buffer though, which is annoying.
I just setup something like this for the >termwidth input:


if (cmdbufpos > (COLS)) {
clrtoeol();
lline = 1;
printw("%s", inbuf + COLS);
} else {
printw("%s", inbuf);
}


Quote
I'm using 4 windows.  2 for the chat area, and 2 for the input area.  The first one in each set is for the border, and the second one is for the actual data.  Here is the code I use for adding it to the window:

You'll have a much easier time with this than I did. I didn't use windows so I couldn't set a scrollable attribute. I had to implement that myself.

I doubt I'm going to implement arrow keys or bigger-than-screen.  At least, not for this project.  But that reminds me of another point: do you allow terminal resizing?  That explodes mine, personally, I don't know if it's possible to deal with that..

And you should use windows (NOT to be confused with Windows), it makes things a lot easier. 

(I'm sure people will quote me on, "you should use windows...., it makes things a lot easier" :-)
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


Kp

Quote from: iago on November 20, 2005, 04:10 AMI doubt I'm going to implement arrow keys or bigger-than-screen.  At least, not for this project.  But that reminds me of another point: do you allow terminal resizing?  That explodes mine, personally, I don't know if it's possible to deal with that..

You might be able to handle it by processing SIGWINCH (window size change notify), if your terminal emulator supports it.  Look into resizeterm(3ncurses) and wresize(3ncurses).  Beware that resizeterm is an ncurses extension, so it might not be particularly portable.

Quote from: iago on November 20, 2005, 04:10 AMAnd you should use windows (NOT to be confused with Windows), it makes things a lot easier.

(I'm sure people will quote me on, "you should use windows...., it makes things a lot easier" :-)

You might've been a bit better off if you'd specified "use ncurses windows," so that anyone taking you out of context would be forced to add another ellipsis between "use" and "windows," thereby making it more obvious that the quote was misleading (and if they failed to add the ellipsis and simply dropped the word ncurses, then they're be guilty of outright misquotation, instead of merely misleading quotation)!
[19:20:23] (BotNet) <[vL]Kp> Any idiot can make a bot with CSB, and many do!

iago

Quote from: Kp on November 20, 2005, 12:06 PM
Quote from: iago on November 20, 2005, 04:10 AMI doubt I'm going to implement arrow keys or bigger-than-screen.  At least, not for this project.  But that reminds me of another point: do you allow terminal resizing?  That explodes mine, personally, I don't know if it's possible to deal with that..

You might be able to handle it by processing SIGWINCH (window size change notify), if your terminal emulator supports it.  Look into resizeterm(3ncurses) and wresize(3ncurses).  Beware that resizeterm is an ncurses extension, so it might not be particularly portable.
Ah, cool!  One thing I noticed is that if I resize while select() is waiting, select() fails with the error "Interrupted system call".  That's annoying, since I have to treat that as not an error condition, and just loop to select() again.  I wonder if select() returning -1 is ever an indication that I can't use select() anymore at all..

Quote from: Kp on November 20, 2005, 12:06 PM
Quote from: iago on November 20, 2005, 04:10 AMAnd you should use windows (NOT to be confused with Windows), it makes things a lot easier.

(I'm sure people will quote me on, "you should use windows...., it makes things a lot easier" :-)

You might've been a bit better off if you'd specified "use ncurses windows," so that anyone taking you out of context would be forced to add another ellipsis between "use" and "windows," thereby making it more obvious that the quote was misleading (and if they failed to add the ellipsis and simply dropped the word ncurses, then they're be guilty of outright misquotation, instead of merely misleading quotation)!
Haha, yeah, but that's not as fun.  :-)
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


Kp

Quote from: iago on November 20, 2005, 12:41 PM
Quote from: Kp on November 20, 2005, 12:06 PM
Quote from: iago on November 20, 2005, 04:10 AMI doubt I'm going to implement arrow keys or bigger-than-screen.  At least, not for this project.  But that reminds me of another point: do you allow terminal resizing?  That explodes mine, personally, I don't know if it's possible to deal with that..
You might be able to handle it by processing SIGWINCH (window size change notify), if your terminal emulator supports it.  Look into resizeterm(3ncurses) and wresize(3ncurses).  Beware that resizeterm is an ncurses extension, so it might not be particularly portable.
Ah, cool!  One thing I noticed is that if I resize while select() is waiting, select() fails with the error "Interrupted system call".  That's annoying, since I have to treat that as not an error condition, and just loop to select() again.  I wonder if select() returning -1 is ever an indication that I can't use select() anymore at all..

Have you tried setting SA_RESTART in the sa_flags member of the struct sigaction that you pass to sigaction(2)?  The manpage says it makes some system calls restartable across signals, but doesn't specify which calls or which signals it affects.

Based on the documentation for select(2), EINTR looks like it's probably the only error code from which you can recover easily.  However, I can see some argument for picking out the other error codes in more detail.  For instance, you might want to sleep or exit cleanly if you get ENOMEM, but call abort(3) if you get EBADF or EINVAL.  I've never found a way to get EBADF or EINVAL without some logic error elsewhere in my program, so aborting on those error codes is handy to capture a core file so I can try to trace what caused the error code.
[19:20:23] (BotNet) <[vL]Kp> Any idiot can make a bot with CSB, and many do!

iago

Quote from: Kp on November 20, 2005, 01:35 PM
Quote from: iago on November 20, 2005, 12:41 PM
Quote from: Kp on November 20, 2005, 12:06 PM
Quote from: iago on November 20, 2005, 04:10 AMI doubt I'm going to implement arrow keys or bigger-than-screen.  At least, not for this project.  But that reminds me of another point: do you allow terminal resizing?  That explodes mine, personally, I don't know if it's possible to deal with that..
You might be able to handle it by processing SIGWINCH (window size change notify), if your terminal emulator supports it.  Look into resizeterm(3ncurses) and wresize(3ncurses).  Beware that resizeterm is an ncurses extension, so it might not be particularly portable.
Ah, cool!  One thing I noticed is that if I resize while select() is waiting, select() fails with the error "Interrupted system call".  That's annoying, since I have to treat that as not an error condition, and just loop to select() again.  I wonder if select() returning -1 is ever an indication that I can't use select() anymore at all..

Have you tried setting SA_RESTART in the sa_flags member of the struct sigaction that you pass to sigaction(2)?  The manpage says it makes some system calls restartable across signals, but doesn't specify which calls or which signals it affects.
Nope, I have yet to play with signals yet.  I'll give that a try when I do it, though.

Quote from: Kp on November 20, 2005, 01:35 PM
Based on the documentation for select(2), EINTR looks like it's probably the only error code from which you can recover easily.  However, I can see some argument for picking out the other error codes in more detail.  For instance, you might want to sleep or exit cleanly if you get ENOMEM, but call abort(3) if you get EBADF or EINVAL.  I've never found a way to get EBADF or EINVAL without some logic error elsewhere in my program, so aborting on those error codes is handy to capture a core file so I can try to trace what caused the error code.
Ah, that's easy enough, then.  I'm not terribly worried about error handling in this project, but I might look into it if I have extra time.  Thanks! :)
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*