select on fds higher then 255 do not check if the fd is open. Here is my example code:
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/select.h>
int main()
{
fd_set set;
for(int i = 5;i<FD_SETSIZE;i++)
{
printf("--> i is %d\n", i);
FD_ZERO(&set);
FD_SET(i, &set);
close(i);
int retval = select(FD_SETSIZE, &set, NULL, NULL, NULL);
if(-1 == retval)
{
perror("select");
}
}
}
This results in:
--> i is 5
select: Bad file descriptor
...
--> i is 255
select: Bad file descriptor
--> i is 256
Then the application blocks.
Why does this not create a EBADF on 256 till FD_SETSIZE?
Requested Information from comments:
The result of prlimit is:
NOFILE max number of open files 1024 1048576
This is the result of strace ./test_select:
select(1024, [127], NULL, NULL, NULL) = -1 EBADF (Bad file descriptor)
dup(2) = 3
fcntl(3, F_GETFL) = 0x8402 (flags O_RDWR|O_APPEND|O_LARGEFILE)
fstat(3, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
write(3, "select: Bad file descriptor\n", 28select: Bad file descriptor
) = 28
close(3) = 0
write(1, "--> i is 128\n", 13--> i is 128
) = 13
close(128) = -1 EBADF (Bad file descriptor)
select(1024, [128], NULL, NULL, NULL
Debunking thoughts from the comments:
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/select.h>
#include <fcntl.h>
int main()
{
char filename[80];
int fd;
for(int i = 5;i<500;i++)
{
snprintf(filename, 80, "/tmp/file%d", i);
fd = open(filename, O_RDWR | O_APPEND | O_CREAT);
}
printf("--> fd is %d, FD_SETSIZE is %d\n", fd, FD_SETSIZE);
fd_set set;
FD_ZERO(&set);
FD_SET(fd, &set);
int retval = select(FD_SETSIZE, NULL, &set, NULL, NULL);
if(-1 == retval)
{
perror("select");
}
}
Results in:
$ ./test_select
--> fd is 523, FD_SETSIZE is 1024
Process exits normally, no blocking.
Something very strange is going on here. You may have found a bug in the Linux kernel.
I modified your test program to make it more precise and also to not get stuck when it hits the problem:
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
int main(void)
{
fd_set set;
struct timeval tv;
int i;
for(i = 5; i < FD_SETSIZE; i++)
{
FD_ZERO(&set);
FD_SET(i, &set);
tv.tv_sec = 0;
tv.tv_usec = 1000;
close(i);
int retval = select(FD_SETSIZE, &set, 0, 0, &tv);
if (retval == -1 && errno == EBADF)
;
else
{
if (retval > 0)
printf("fd %d: select returned success (%d)\n", i, retval);
else if (retval == 0)
printf("fd %d: select timed out\n", i);
else
printf("fd %d: select failed (%d; %s)\n", i, retval, strerror(errno));
return 1;
}
}
return 0;
}
My understanding of POSIX says that, whatever FD_SETSIZE is, this program should produce no output and exit successfully. And that is what it does on FreeBSD 11.1 and NetBSD 7.1 (both running on x86 processors of some description). But on Linux (x86-64, kernel 4.13), it prints
fd 256: select timed out
and exits unsuccessfully. Even stranger, if I run the same binary under strace, that changes the output:
$ strace -o /dev/null ./a.out
fd 64: select timed out
The same thing happens if I run it under gdb, even if I don't tell gdb to do anything other than just run the program.
Reading symbols from ./a.out...done.
(gdb) r
Starting program: /tmp/a.out
fd 64: select timed out
[Inferior 1 (process 8209) exited with code 01]
So something is changing just because the process is subject to ptrace monitoring. That can only be caused by the kernel.
I have filed a bug report on the Linux kernel and will report what they say about it.
Related
Can one do non-blocking I/O on a pipe? fcntl fails to set O_NONBLOCK. Page 918 of The Linux Programming Interface includes a table 'Semantics of reading n bytes from pipe or FIFO (p)'. This table lists the behaviour of pipes and FIFO's with one column titled O_NONBLOCK enabled? This would imply that you can set the O_NONBLOCK flag on a pipe. Is this correct? The following code fails to set the flag, fcntl(2) does not report an error though.
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
int main()
{
int fds[2];
pid_t pid;
char wr_buf[100];
char rd_buf[100];
pipe(fds);
pid = fork();
if ( pid )
{
while (1 )
{
memcpy( wr_buf, "abcdefghi\0",10);
write( fds[1], wr_buf, 10);
sleep(2);
}
}
else
{
int retval = fcntl( fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK);
printf("Ret from fcntl: %d\n", retval);
while (1)
{
ssize_t r=read( fds[0], rd_buf, 10 );
printf("read: %d\n", r);
if ( r > 0 )
{
printf("Buffer: %s\n", rd_buf);
}
else
{
printf("Read nothing\n");
perror("Error was");
sleep(1);
}
}
}
}
There is nothing special to pipe and O_NONBLOCK. The following example work as expected. I did not check every retval from every call to make the example a bit more readable. A real world application must do the checks.
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
int main()
{
int fds[2];
pid_t pid;
char buf[100];
pipe(fds);
pid = fork();
if ( pid )
{
while (1 )
{
memcpy( buf, "abcdefghi\0",10);
write( fds[1], buf, 10);
sleep(2);
}
}
else
{
int retval = fcntl( fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK);
printf("Ret from fcntl: %d\n", retval);
while (1)
{
ssize_t r=read( fds[0], buf, 10 );
printf("read: %d\n", r);
if ( r > 0 )
{
printf("Buffer: %s\n", buf);
}
else
{
printf("Read nothing\n");
perror("Error was");
sleep(1);
}
}
}
}
After writing my example I inspect your code and found:
flags = fcntl(pfd[0], F_GETFD);
flags |= O_NONBLOCK;
if (fcntl(pfd[0], F_SETFD, flags))
Please change F_SETFD to F_SETFL and also for the get operation. You would not change the file descriptor flags but the file status flags :-)
From man 3 fcntl:
File descriptor flags
The following commands manipulate the flags associated with a file
descriptor. Currently, only one such flag is defined: FD_CLOEXEC, the
close-on-exec flag. If the FD_CLOEXEC bit is 0, the file descriptor
will remain open across an execve(2), otherwise it will be closed.
File status flags
Each open file description has certain associated status flags, ini‐
tialized by open(2) and possibly modified by fcntl(). Duplicated file
descriptors (made with dup(2), fcntl(F_DUPFD), fork(2), etc.) refer to
the same open file description, and thus share the same file status
flags.
F_SETFL (int)
Set the file status flags to the value specified by arg. File
access mode (O_RDONLY, O_WRONLY, O_RDWR) and file creation flags
(i.e., O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC) in arg are ignored.
On Linux this command can change only the O_APPEND, O_ASYNC,
O_DIRECT, O_NOATIME, and O_NONBLOCK flags. It is not possible
to change the O_DSYNC and O_SYNC flags; see BUGS, below.
When I create a shared memory (c program in Linux) I delete it with shmctl(shmid, IPC_RMID, 0) and everything looks fine when I'm using ipcs -m to check if there are any remaining shared memory segments. But I'm wondering how can I delete my semaphores that I've created right before the program is terminated because when I'm using ipcs -s I see both of my semaphores right there, result:
------ Semaphore Arrays --------
key semid owner perms nsems
0x6b014021 0 benjamin 600 1
0x6c014021 1 benjamin 600 1
Thanks.
You can use semget and semctl after setting KEY to the right value returned by ipcs -s:
#define KEY 0x...
int id, rc;
id = semget(KEY, 1, IPC_STAT);
if (id < 0)
{
perror("semget");
exit(1);
}
rc = semctl(id, 1, IPC_RMID);
if (rc < 0)
{
perror("semctl");
exit(1);
}
Or use directly semctl with id returned by ipcs -s:
rc = semctl(id, 1, IPC_RMID);
if (rc < 0)
{
perror("semctl");
exit(1);
}
Full C program:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <errno.h>
int main(int argc, char **argv){
int id, rc;
id = atoi(argv[1]);
printf("id=%d\n", id);
rc = semctl(id, 1, IPC_RMID);
if (rc < 0)
{
perror("semctl");
exit(1);
}
exit(0);
}
Execution:
$ ipcs -s
------ Tableaux de sémaphores --------
clef semid propriétaire perms nsems
0x00001111 393221 pifor 666 1
$ ./rsem 393221
id=393221
$ ipcs -s
------ Tableaux de sémaphores --------
clef semid propriétaire perms nsems
I use a device on serial port /dev/ttyUSB0 (uses FTDI) and I don't want to leak any file descriptors to other spawned processes so I set the close-on-exec flag on the descriptor. Could you tell me what is the difference between setting O_CLOEXEC while opening:
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
int main()
{
int fd, rc;
fd = open("/dev/ttyUSB1", O_RDWR | O_NOCTTY | O_CLOEXEC);
if(fd < 0)
{
perror("error open:");
exit(-1);
}
rc = close(fd);
if(rc != 0)
{
perror("error close:");
exit(-1);
}
return 0;
}
And setting close-on-exec with ioctl(fd, TIOCEXCL):
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <stdio.h>
int main()
{
int fd, rc;
fd = open("/dev/ttyUSB1", O_RDWR | O_NOCTTY);
if(fd < 0)
{
perror("error open:");
exit(-1);
}
rc = ioctl(fd, TIOCEXCL);
if(rc != 0)
{
perror("error ioctl:");
exit(-1);
}
rc = close(fd);
if(rc != 0)
{
perror("error close:");
exit(-1);
}
return 0;
}
The TIOCEXCL does not set the close-on-exec flag (that would be FIOCLEX, or, equivalently, fcntl(fd, F_SETFD, FD_CLOEXEC)).
To answer the question you thought you were asking:
Specifying O_CLOEXEC when you open() a file will set the close-on-exec flag before it returns, saving you another call and, importantly, ensuring that there is no race condition where another thread might call exec() after open() but before the subsequent fcntl().
If you really need to set or unset the flag at any other time (I've never needed to), you can do so with fcntl F_SETFD, passing FD_CLOEXEC or 0 respectively.
Can one do non-blocking I/O on a pipe? fcntl fails to set O_NONBLOCK. Page 918 of The Linux Programming Interface includes a table 'Semantics of reading n bytes from pipe or FIFO (p)'. This table lists the behaviour of pipes and FIFO's with one column titled O_NONBLOCK enabled? This would imply that you can set the O_NONBLOCK flag on a pipe. Is this correct? The following code fails to set the flag, fcntl(2) does not report an error though.
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
int main()
{
int fds[2];
pid_t pid;
char wr_buf[100];
char rd_buf[100];
pipe(fds);
pid = fork();
if ( pid )
{
while (1 )
{
memcpy( wr_buf, "abcdefghi\0",10);
write( fds[1], wr_buf, 10);
sleep(2);
}
}
else
{
int retval = fcntl( fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK);
printf("Ret from fcntl: %d\n", retval);
while (1)
{
ssize_t r=read( fds[0], rd_buf, 10 );
printf("read: %d\n", r);
if ( r > 0 )
{
printf("Buffer: %s\n", rd_buf);
}
else
{
printf("Read nothing\n");
perror("Error was");
sleep(1);
}
}
}
}
There is nothing special to pipe and O_NONBLOCK. The following example work as expected. I did not check every retval from every call to make the example a bit more readable. A real world application must do the checks.
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
int main()
{
int fds[2];
pid_t pid;
char buf[100];
pipe(fds);
pid = fork();
if ( pid )
{
while (1 )
{
memcpy( buf, "abcdefghi\0",10);
write( fds[1], buf, 10);
sleep(2);
}
}
else
{
int retval = fcntl( fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK);
printf("Ret from fcntl: %d\n", retval);
while (1)
{
ssize_t r=read( fds[0], buf, 10 );
printf("read: %d\n", r);
if ( r > 0 )
{
printf("Buffer: %s\n", buf);
}
else
{
printf("Read nothing\n");
perror("Error was");
sleep(1);
}
}
}
}
After writing my example I inspect your code and found:
flags = fcntl(pfd[0], F_GETFD);
flags |= O_NONBLOCK;
if (fcntl(pfd[0], F_SETFD, flags))
Please change F_SETFD to F_SETFL and also for the get operation. You would not change the file descriptor flags but the file status flags :-)
From man 3 fcntl:
File descriptor flags
The following commands manipulate the flags associated with a file
descriptor. Currently, only one such flag is defined: FD_CLOEXEC, the
close-on-exec flag. If the FD_CLOEXEC bit is 0, the file descriptor
will remain open across an execve(2), otherwise it will be closed.
File status flags
Each open file description has certain associated status flags, ini‐
tialized by open(2) and possibly modified by fcntl(). Duplicated file
descriptors (made with dup(2), fcntl(F_DUPFD), fork(2), etc.) refer to
the same open file description, and thus share the same file status
flags.
F_SETFL (int)
Set the file status flags to the value specified by arg. File
access mode (O_RDONLY, O_WRONLY, O_RDWR) and file creation flags
(i.e., O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC) in arg are ignored.
On Linux this command can change only the O_APPEND, O_ASYNC,
O_DIRECT, O_NOATIME, and O_NONBLOCK flags. It is not possible
to change the O_DSYNC and O_SYNC flags; see BUGS, below.
Here is a simple select() loop:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/select.h>
#define BUFSIZE 999
int main()
{
int select_result;
fd_set read_fds, write_fds;
struct timeval timeout = {0, 400000}, timeoutcopy;
const int max_fd = STDIN_FILENO > STDOUT_FILENO ? STDIN_FILENO :
STDOUT_FILENO;
char buffer[BUFSIZE];
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
fcntl(STDOUT_FILENO, F_SETFL, fcntl(STDOUT_FILENO, F_GETFL) | O_NONBLOCK);
printf("Enter loop\n");
while(1) {
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
printf("Loop\n");
FD_SET(STDIN_FILENO, &read_fds);
FD_SET(STDOUT_FILENO, &write_fds);
timeoutcopy = timeout;
if ((select_result = select(max_fd, &read_fds, &write_fds, NULL,
&timeoutcopy)) < 0) {
return select_result;
}
if (FD_ISSET(STDIN_FILENO, &read_fds))
printf("Stdin ready for read\n");
fgets(buffer, BUFSIZE, stdin);
if (strlen(buffer))
printf("Stdin content: %s\n", buffer);
if (FD_ISSET(STDOUT_FILENO, &write_fds))
printf("Stdout ready for write\n");
}
}
It just polls stdin and stdout with select() with timeout of 400000 milliseconds. When stdin is ready it tries to read it's content and print it. When stdout is ready, it just prints, that it it ready.
And for some reason, after select() call stdin is never ready, why?
Your max_fd should be "the highest-numbered file descriptor in any of the three sets, plus 1." according to the select man page. I should rename it so you'll remember to add 1.
max_fd must be incremented by 1. The name is indeed confusing, "nfds" is probably more clear. Please have a look here:
select man page