Why 20 is omitted from the file descriptor - c

EDIT:
lsof shows that it's opened by ptmx. Thanks to #zwol
My code below prints the file descriptor returned from open. I noticed that 20 is missing. There is no similar question to the best of my knowledge.
background:
Filesystem: ext4
Ubuntu 20.04 on WSL2
code:
int main()
{
char name[2] = "a";
for (int i = 0; i < 52; i++) {
int fd = open(name, O_RDWR | O_CREAT, 0644);
printf("fd is %d\n", fd);
}
return 0;
}
output:
$ ./a.out
fd is 3
fd is 4
fd is 5
...
fd is 18
fd is 19
fd is 21 <-- here
fd is 22
...
lsof
...
a.out 1815 ryan 19u REG 8,16 0 42321 /tmp/tmp/a
a.out 1815 ryan 20u CHR 5,2 0t0 15832 /dev/ptmx
a.out 1815 ryan 21u REG 8,16 0 42321 /tmp/tmp/a
...
I have 2 questions:
What's the reason behind it? (What triggers ptmx)
Will there be more indices missing if I keep opening files? (More programs like ptmx?)

POSIX says that every system call that allocates file descriptors must use the lowest number(s) that are not already in use. Therefore, descriptor number 20 must have already been open. Your sample program doesn't open anything before the loop, so it must have been inherited from your shell, or opened by the C library prior to main.
You can find out more by having your program print its PID and then sleep for a long time, after the loop, and then running lsof on it while it's sleeping.

Related

File descriptors in linux; Pwnable kr challenge [duplicate]

This question already has answers here:
Writing to stdin and reading from stdout (UNIX/LINUX/C Programming)
(5 answers)
Closed 10 months ago.
In order to understand the workings of C and linux, I was doing the pwnable.kr challenges, but there is something in the first challenge that doesn't quite add to me.
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
if(argc<2){
printf("pass argv[1] a number\n");
return 0;
}
int fd = atoi( argv[1] ) - 0x1234;
int len = 0;
len = read(fd, buf, 32);
if(!strcmp("LETMEWIN\n", buf)){
printf("good job :)\n");
system("/bin/cat flag");
exit(0);
}
printf("learn about Linux file IO\n");
return 0;
}
To my understanding, the 0 fd is for stdin, and the 1 and 2 fd are for stdout, yet all three of them allow me to solve the challenge. I understand the first one, but wouldn't the other 2 file descriptors just read what I print on my promt, and not allow me to write anything after?
Let's take a look at what /proc says:
$ ls -al /proc/self/fd
total 0
dr-x------. 2 mrsam mrsam 0 Apr 23 15:25 .
dr-xr-xr-x. 9 mrsam mrsam 0 Apr 23 15:25 ..
lrwx------. 1 mrsam mrsam 64 Apr 23 15:25 0 -> /dev/pts/1
lrwx------. 1 mrsam mrsam 64 Apr 23 15:25 1 -> /dev/pts/1
lrwx------. 1 mrsam mrsam 64 Apr 23 15:25 2 -> /dev/pts/1
For an interactive terminal all three file descriptors are, basically the same. All three are connected to the terminal device, after all, why won't they be? Input and output goes to the same screen.
And, for simplicity, you can think of them as dup()s of each other. So, as a side effect, all three file descriptors are read/write.
Now, of course, this is only true for programs launched from an interactive terminal. In an arbitrary program, launched in an unknown environment you can only rely on being able to read from 0 and write to 1 and 2.

Unix/Linux pipe behavior when reading process terminates before writing process

I have this:
$ ls -lh file
-rw-r--r-- 1 ankur root 181M Sep 23 20:09 file
$ head -6 file
z
abc
abc
abc
abc
abc
$ cat file | grep -m 1 z
z
Question:
Why is the cat command line in the last line not dying prematurely with SIGPIPE? I think this should happen because grep terminates in no time compared to cat file that cats 183MB of file. With reading process gone cat will try to write to a broken pipe and should die with SIGPIPE.
Update:
I ended up writing this: readstdin.c
# include <unistd.h>
# include <stdio.h>
int main() {
ssize_t n ;
char a[5];
n = read(0, a, 3);
printf("read %zd bytes\n", n);
return(0);
}
I use it like this:
$ cat file | ./readstdin
$ yes | ./readstdin
But still cat or yes does not die prematurely. I expect it to because by reading process is terminating before writing process is done writing.
If the read end of some pipe(2) is close(2)-ed, further write(2)s will get a SIGPIPE signal(7). Read also pipe(7).
They would get the SIGPIPE when the pipe buffer becomes full.
In the yes | ./readstdin command, the yes command gets a a SIGPIPE signal. Just try yes in a terminal, it spits some output indefinitely ad nauseam till you kill it.
In the cat file | ./readstdin command, it could happen (notably if file is quite small, less that sysconf(_POSIX_PIPE_BUF) bytes, which might be 4096 bytes), that the cat command is close(2)-ing the STDOUT_FILENO descriptor and that the pipe is still not full. Then cat may not get any SIGPIPE.
Normal processes close the input stream causing a SIGPIPE. In the man page, it mentions that -m stops reading, and "ensures that standard input is positioned to just after the last matching line before exiting". So it doesn't actually close the stream. You can demonstrate like this:
cat file | (grep -m1 z && grep -m1 c)
You'll get the first c after the first z, which is sometimes useful. After the last grep exits, there is no place for the stream to go, so it's left unread and the whole group of commands exits. You can demonstrate:
(while true; do echo z; sleep 1; done) | grep -m3 z
(while true; do echo z; sleep 1; done) | grep --line-buffered z | head -3

broken pipe between cat and grep on Raspberry Pi

I am trying to read the wireless connection link quality in a C program.
I do:
for(;;){
rf_line = popen("cat /proc/net/wireless | grep wlan0", "r");
fgets(line, 80, rf_line);
pclose(rf_line);
...
//more code
}
On the Raspberry Pi with the Wheezy 08/12 image, it crashes with the errno 32, broken pipe.
If I call cat /proc/net/wireless | grep wlan0 from the console, it works fine.
Also if I am trying to debug with gdb, the error does not occur.
On my Laptop with Linux Mint 14 it does never occur.
I tried to prevent this by making a system(...) call. Even with opening a new bash with bash -c ....
I also tried, not to write to the stdout and into a file, without luck.
edit:
with
rf_line = popen("/bin/grep wlan0 /proc/net/wireless", "r");
Increasing the size of the line to 200 had no effect.
I have the same result, errno 32.
edit:
with
rf_line = fopen("/proc/net/wireless","r");
I have the same result, errno 32.
This however leads to the suggestion, that popen does not work correctly, because it is the only pipe left.
Fopen / popen is called very frequently, could this be the issue?
Has anybody a clou, what I could do next?
regards,
Ck
From man 2 write:
EPIPE: fd is connected to a pipe or socket whose reading end is closed. When this happens the writing process will also receive a SIGPIPE signal. (Thus, the write return value is seen only if the program catches, blocks or ignores this signal.)
So the command, that you executed with popen("...") (not your program) will get the EPIPE error on it's write() and the SIGPIPE signal if you close your end of the pipe before it finishes to write anything.
You need to read every line of the command output, not just first one. Until you'll get EOF.
I'm not really sure how popen works but try. Perhaps it just doesn't work with pipes.
rf_line = popen("grep wlan0 /proc/net/wireless", "r");
Another is to add absolute path like:
rf_line = popen("/bin/grep wlan0 /proc/net/wireless", "r");
Or
rf_line = popen("/usr/bin/grep wlan0 /proc/net/wireless", "r");
UPDATE
This one worked for me.
#include <stdio.h>
int main(int argc, char** argv)
{
char line[200];
line[0] = '\0';
FILE* rf_line = popen("grep wlan0 /proc/net/wireless", "r");
fgets(line, 200, rf_line);
printf("%s", line); /* You can remove this */
pclose(rf_line);
}

"dup" function, "more" and redirection

I have a problem with this little code for educational purposes. I can not understand how it works.
#include <stdio.h>
#include <fcntl.h>
#define FNAME "info.txt"
#define STDIN 0
int main(){
int fd;
fd = open(FNAME, O_RDONLY);
close(STDIN); //entry 0 on FDT is now free
dup(fd); //fd duplicate is now stored at entry 0
execlp("more","more",0);
}
By starting this program it prints the contents of the file "info.txt" on terminal. I can not understand why! Where is the link between "more" and STDIN (keyboard or file)?
Why if i use more with no args and without redirection on file it just shows a help screen but whit redirection it uses the file as input?
dup always gives you the lowest available file descriptor number.
By default all process will have 0, 1 and 2 for stdin, stdout and stderr. You are opening a file from that you will get a file descriptor value 3. After that you have closed stdin. Now calling dup after that will give you a lowest available value as a duplicate file descriptor for 3, so you will be getting stdin as duplicate file descriptor for 3.
int main()
{
int fd, fd2;
fd = open(FNAME, O_RDONLY); //This will be 3
fd2 = dup(fd); //This will be 4 because 4 is the lowest available value
close(STDIN); //entry 0 on FDT is now free
dup(fd); //fd duplicate is now stored at entry 0
execlp("more","more",0);
}
And here why its displaying the content of the file is, more command can be used in two ways.
more filename
command | more
In your exec, you are not giving any filename as command line argument for more command. So its executing in pipe mode, by reading it from stdin.

Why does open() create my file with the wrong permissions?

I am trying to read some text from a file and write it to another using open(), read() and write().
This is my open() for the file-to-write-to (I want to create a new file and write into it):
fOut = open ("test-1", O_RDWR | O_CREAT | O_SYNC);
This is setting file-permissions to something I don't understand at all. This is the output of ls -l:
---------T 1 chaitanya chaitanya 0 2010-02-11 09:38 test-1
Even the read permission is locked. I tried searching for this, but could not find ANYTHING.
Strangely, write() still successfully writes data to the file.
Also, if I do a 'chmod 777 test-1', things start working properly again.
Could someone please let me know where I am going wrong in my open call?
Thanks!
For your reference, I have pasted the complete program below:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main () {
char buffer[512], ch;
int fIn, fOut, i;
ssize_t bytes;
FILE *fp = NULL;
//open a file
fIn = open ("test", O_RDONLY);
if (fIn == -1) {
printf("\nfailed to open file.");
return 1;
}
//read from file
bytes = read (fIn, buffer, sizeof(buffer));
//and close it
close (fIn);
printf("\nSuccessfully read %d bytes.\n", bytes);
//Create a new file
fOut = open ("test-1", O_RDWR | O_CREAT | O_SYNC);
printf("\nThese are the permissions for test-1\n");
fflush(stdout);
system("ls -l test-1");
//write to it and close it.
write (fOut, buffer, bytes);
close (fOut);
//write is somehow locking even the read permission to the file. Change it.
system("chmod 777 test-1");
fp = fopen ("test-1", "r");
if (fp == NULL) {
printf("\nCan't open test-1");
return 1;
}
while (1)
{
ch = fgetc(fp);
if (ch == EOF)
break;
printf("\n%c", ch);
}
fclose (fp);
return 0;
}
open() takes a third argument which is the set of permissions, i.e.
open(filename, O_RDWR|O_CREAT, 0666)
0666 is an octal number, i.e. every one of the 6's corresponds to three permission bits
6 = rw
7 = rwx
first three bits for owner permission, next three bits for group permission and next is for the world
the first digit - represents that is file or directory. (0 - file, d - directory)
here we used 0 means file
It's a typical pitfall. The compiler allows you to leave the permission argument away because when you open an existing file the permission bits don't make sense. But when you forget the argument when you create a file, you get a random set of permissions, e.g. 0000 in your case (---).
Reading http://linux.die.net/man/2/open it seems you missed the mode parameter for open:
mode must be specified when O_CREAT is in the flags, and is ignored otherwise.
The argument mode specifies the permissions to use in case a new file is created.
This question recently helped me out, so I wanted to do my part to add a bit more depth as to what's going on. Like it was stated before, you were missing the third argument to open(). However, the permissions you see aren't random; they're coming from the stack. Look at the following code snippet:
asm("push $0");
asm("push $0");
asm("push $0");
fd = open("base", O_RDWR|O_CREAT);
Note the following result:
----------. 1 user user 4 Feb 26 08:21 base
Let's change the first push to 1, i.e. execute permission:
asm("push $1;push $0;push $0");
fd = open("base", O_RDWR|O_CREAT);
and we get:
---------x. 1 user user 4 Feb 26 08:25 base
Change the push to 4, i.e. read permission, and mess with the other two values:
asm("push $4;push $5;push $6");
fd = open("base", O_RDWR|O_CREAT);
and we get:
-------r--. 1 user user 4 Feb 26 08:27 base
Thus we can see the third value popped off the stack (first pushed) is what really matters. Finally for fun we can try 5 and then 50, which respectively result in:
-------r-x. 1 user user 4 Feb 26 08:27 base
----rw----. 1 user user 4 Feb 26 08:28 base
Hope this adds some clarity!
Actually umask() only filters permissions and does not set them. The typical umask() value is 0002 ("don't give away write permission to the world") and if your mode value in the open( "file", O_CREAT, 0777) gave all permissions, the resulting file would have 775 as its permssions.
Not strictly relevant to the question, but the accepted answer could use this clarifying point:
There is a relationship between rwx and its numerical representation that can be seen by treating the presence of a letter as a binary 1, and its absence as a binary 0.
e.g.
rwx <--> 111 (binary) <--> 7 (octal)
r-- <--> 100 (binary) <--> 4 (octal)
-wx <--> 011 (binary) <--> 3 (octal)
As a further addendum, you may now consider the chmod command:
chmod 777 filename.extension --> rwxrwxrwx permissions
777 <--> 111 111 111 <--> rwx rwx rwx
or: chmod 654 filename.extension --> rw-r-x-r--
654 <--> 110 101 100 <--> rw- r-x r--
Hope this is informative!
you can call umask(0); system call before using open(); system call to set your choices permissions to file correctly.
This is kind of an old thread, but I think people should be aware of the "sys/stat.h" library. This includes a bunch of symbolic constants for setting permission bits.
For example: To open a file with Read/Write permissions enabled for the user
#include <fcntl.h>
#include <sys/stat.h>
open("Your/File/Path", O_RDWR | O_CREAT, S_IWUSR | S_IRUSR);
where:
S_IWUSR // Sets the Users Write bit
S_IRUSR // Sets the Users Read bit
This library includes a bunch of others, I won't list them all here but you can read up on it all here.
Of course you can put in the octal values to set these bits, however some may argue that it is poor coding practice.

Resources