How do file descriptors work?
Can someone tell me why this does not work? I’m playing around with file descriptors, but feel a little lost.
#!/bin/bash echo "This" echo "is" >&2 echo "a" >&3 echo "test." >&4
4 Answers 4
File descriptors 0, 1 and 2 are for stdin, stdout and stderr respectively.
File descriptors 3, 4, .. 9 are for additional files. In order to use them, you need to open them first. For example:
exec 3<> /tmp/foo #open fd 3. echo "test" >&3 exec 3>&- #close fd 3.
That’s what I’m looking for! So I need to specify a file for it to use as a temporary storage place with the exec command, and then close them when i’m done? Sorry, I’m a little fuzzy with the exec command, I don’t use it much.
That could work out nicely then, I’m trying to port some scripts to be compatible with crontab task scheduler, but I am having trouble as cron does not allow for the piping of stdout in scripts.
You don’t have to specify a file. You can also make fd 3 point to the same file that fd 1 points to exec 3>&1 causes echo hi >&3 to print «hi» to stdout.
@clapas , 3<> is for reading and writing. Since we only write to this example, then yes 3> would be correct in this snippet; however, typically we want to read from the descriptor something we wrote to.
It’s an old question but one thing needs clarification.
While the answers by Carl Norum and dogbane are correct, the assumption is to change your script to make it work.
What I’d like to point out is that you don’t need to change the script:
#!/bin/bash echo "This" echo "is" >&2 echo "a" >&3 echo "test." >&4
It works if you invoke it differently:
which means to redirect file descriptors 3 and 4 to 1 (which is standard output).
The point is that the script is perfectly fine in wanting to write to descriptors other than just 1 and 2 (stdout and stderr) if those descriptors are provided by the parent process.
Your example is actually quite interesting because this script can write to 4 different files:
./fdtest >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt
Now you have the output in 4 separate files:
$ for f in file*; do echo $f:; cat $f; done file1.txt: This file2.txt: is file3.txt: a file4.txt: test.
What is more interesting about it is that your program doesn’t have to have write permissions for those files, because it doesn’t actually open them.
For example, when I run sudo -s to change user to root, create a directory as root, and try to run the following command as my regular user (rsp in my case) like this:
# su rsp -c '../fdtest >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt'
bash: file1.txt: Permission denied
But if I do the redirection outside of su :
# su rsp -c '../fdtest' >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt
(note the difference in single quotes) it works and I get:
# ls -alp total 56 drwxr-xr-x 2 root root 4096 Jun 23 15:05 ./ drwxrwxr-x 3 rsp rsp 4096 Jun 23 15:01 ../ -rw-r--r-- 1 root root 5 Jun 23 15:05 file1.txt -rw-r--r-- 1 root root 39 Jun 23 15:05 file2.txt -rw-r--r-- 1 root root 2 Jun 23 15:05 file3.txt -rw-r--r-- 1 root root 6 Jun 23 15:05 file4.txt
which are 4 files owned by root in a directory owned by root — even though the script didn’t have permissions to create those files.
Another example would be using chroot jail or a container and run a program inside where it wouldn’t have access to those files even if it was run as root and still redirect those descriptors externally where you need, without actually giving access to the entire file system or anything else to this script.
The point is that you have discovered a very interesting and useful mechanism. You don’t have to open all the files inside of your script as was suggested in other answers. Sometimes it is useful to redirect them during the script invocation.
To sum it up, this:
is actually equivalent to:
and running the program as:
The number 1 is just a default number and it is stdout.
can produce a «Bad descriptor» error. How? When run as:
./fdtest2: line 2: echo: write error: Bad file descriptor
Adding >&- (which is the same as 1>&- ) means closing the standard output. Adding 2>&- would mean closing the stderr.
You can even do a more complicated thing. Your original script:
#!/bin/bash echo "This" echo "is" >&2 echo "a" >&3 echo "test." >&4
This is ./fdtest: line 4: 3: Bad file descriptor ./fdtest: line 5: 4: Bad file descriptor
But you can make descriptors 3 and 4 work, but number 1 fail by running:
./fdtest: line 2: echo: write error: Bad file descriptor is a test.
If you want descriptors both 1 and 2 fail, run it like this:
Why? Didn’t anything fail? It did but with no stderr (file descriptor number 2) you didn’t see the error messages!
I think it’s very useful to experiment this way to get a feeling of how the descriptors and their redirection work.
Your script is a very interesting example indeed — and I argue that it is not broken at all, you were just using it wrong! 🙂
Golang bad file descriptor
I am getting a bad file descriptor when trying to append to a logging file within my go routine. write ./log.log: bad file descriptor The file exists and has 666 for permissions. At first I thought well maybe it is because each one of them is trying to open the file at the same time. I implemented a mutex to try and avoid that but got the same issue so I removed it.
logCh := make(chan string, 150) go func() < for < msg, ok := else < logTime := time.Now().Format(time.RFC3339) if _, err := f.WriteString(logTime + " - " + msg); err != nil < fmt.Print(err) >f.Close() > > else < fmt.Print("Channel closed! \n") break >> >()
3 Answers 3
You need to add the O_WRONLY flag :
if f, err := os.OpenFile("./log.log", os.O_APPEND|os.O_WRONLY, os.ModeAppend); err != nil < /*[. ]*/ >
The argument flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR. These request opening the file read- only, write-only, or read/write, respectively.
O_RDONLY = 0x0 O_RDWR = 0x2 O_WRONLY = 0x1
So by default you get a read-only file descriptor.
This solved my problem, except in my case I had os.O_WRONLY in the os.OpenFile method in a wrapper function, and I was calling that from elsewhere and trying to read from the file it returned, but of course it was set to write-only so I was getting the same error. Fixed by changing to os.O_RDWR .
os.OpenFile(fileName, os.O_CREATE|os.O_APPEND, os.ModePerm)
and it occured the error: bad file descriptor,
then i add the os.O_WRONLY into the function
os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm)
and it did’t occured the problem.
This appears to be a discrepancy between windows and linux. On windows os.O_APPEND implies write access, as can be seen in the code in syscall_windows.go.
in linux the openflags are passed as-is to a linux syscall
So in DOS/WINDOWS you do not need to explicitly add a write flag with an append flag, as it is implied. (this has been default behaviour since the DOS days) Go in linux does need the extra flag added to work.
(. but imo it should not need this flag, as appending implicitly implies that I want to write. Should I bugreport this to golang ?)
Linux Socket Bad File Descriptor
I couldn’t find a duplicate, so I decided to post. We’re getting into Sockets (beginner-level) now, and was given the code below to make the client send a simple message to the server by using send() and recv() . However, everything I have tried doesn’t seem to get rid of the error: Bad File Descriptor and newsockfd always returns the value -1 . I’m confused and have no idea why it doesn’t work. defs.h
#include #include #include #include #include #define SERV_TCP_PORT 6000 #define SERV_HOST_ADDR "127.0.0.1" char *pname;
#include "../defs.h" #include #include #include main(argc, argv) int argc; char *argv[]; < int sockfd; struct sockaddr_in serv_addr; pname=argv[0]; bzero((char*) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=inet_addr(SERV_HOST_ADDR); serv_addr.sin_port=htons(SERV_TCP_PORT); if((sockfd=socket(AF_INET,SOCK_STREAM,0)) < 0) < printf("client: can't open stream socket\n"); exit(0); >if (connect(sockfd,(struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) < printf("client: can't connect to server\n"); exit(0); >send(sockfd,"1",1,0); close(sockfd); exit(0); >
#include "../defs.h" #include #include #include #include void error(char *msg) < perror(msg); exit(1); >main(argc, argv) int argc; char *argv[]; < int sockfd,clilen, childpid; int newsockfd; char buff[255]; struct sockaddr_in cli_addr, serv_addr; if ((sockfd=socket(AF_INET,SOCK_STREAM, 0)) <0) < printf("server: can't open stream socket\n"); exit(0); >bzero((char*) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); serv_addr.sin_port=htons(SERV_TCP_PORT); if(bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) < printf("server: can't bind local address\n"); exit(0); >listen(sockfd, 5); for ( ; ; ) < clilen=sizeof(cli_addr); newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); printf("%d\n",newsockfd); if(newsockfd < 0) < printf("server: accept error\n"); error("ERROR on accept"); exit(0); >if((childpid=fork()) <0) < printf("server: fork error\n"); exit(0); >else if (childpid==0) < close(sockfd); recv(newsockfd,buff,sizeof(buff),0); >close(newsockfd); > >
Linux write to serial port in c++ returns bad file descriptor
I am writing a c++ program to send a receive data from a device located at /dev/ttyACM0. I am able to read from the device without a problem, I can write to it from the command line, but I can not write to it within my c++ program. Here is what I have so far:
#include "SerialComms.h" #include #include SerialComms::SerialComms() < serial_filestream = -1; //CONFIGURE THE PORT //The flags (defined in /usr/include/termios.h - see http://pubs.opengroup.org/onlinepubs/007908799/xsh/termios.h.html): // Baud rate:- B1200, B2400, B4800, B9600, B19200, B38400, B57600, B115200, B230400, B460800, B500000, B576000, B921600, B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000, B4000000 // CSIZE:- CS5, CS6, CS7, CS8 // CLOCAL - Ignore modem status lines // CREAD - Enable receiver // IGNPAR = Ignore characters with parity errors // ICRNL - Map CR to NL on input // PARENB - Parity enable // PARODD - Odd parity (else even) tcgetattr(serial_filestream, &options); options.c_cflag = B9600 | CS8 | CLOCAL | CREAD; //bool SerialComms::Init(string type)< //The flags (defined in fcntl.h): // Access modes (use 1 of these): // O_RDONLY - Open for reading only. // O_RDWR - Open for reading and writing. // O_WRONLY - Open for writing only. // // O_NDELAY / O_NONBLOCK (same function) - Enables nonblocking mode. When set read requests on the file can return immediately with a failure status // if there is no input immediately available (instead of blocking). Likewise, write requests can also return // immediately with a failure status if the output can't be written immediately. // // O_NOCTTY - When set and path identifies a terminal device, open() shall not cause the terminal device to become the controlling terminal for the process. if (type == "USB")< //if type equals USB, open USB serial_filestream = open(USBSerial, O_RDWR | O_NOCTTY | O_NDELAY); //Open in non blocking read/write mode >else if (type == "UART1") < serial_filestream = open(UART1, O_RDWR | O_NOCTTY | O_NDELAY); //Open in non blocking read/write mode >if (serial_filestream == -1) < //ERROR - CAN'T OPEN SERIAL PORT printf("Error - Unable to open: %s\n", type.c_str()); >else < printf("Opened serial type: %s\n", type.c_str()); >tcflush(serial_filestream, TCIFLUSH); tcsetattr(serial_filestream, TCSANOW, &options); printf("Serial Comms for type: %s intialized", type.c_str()); return 1; > bool SerialComms::Ping() < WriteSerial("p,1,1;\n"); return true; >char* SerialComms::ReadSerial() < if (serial_filestream != -1) < // Read up to 255 characters from the port if they are there char rx_buffer[256]; int rx_length = read(serial_filestream, (void*)rx_buffer, 255); //Filestream, buffer to store in, number of bytes to read (max) if (rx_length < 0) < //An error occured (will occur if there are no bytes) >else if (rx_length == 0) < //No data waiting >else < //Bytes received rx_buffer[rx_length] = '\0'; int j = 0; while (rx_buffer[j] != ';' && rx_buffer[j] != '\0' && rx_buffer[j] != '\n')< j++; >char rx_return[j+1]; for(int i = 0; i < j+1; i++)< rx_return[i] = rx_buffer[i]; >rx_return[j+1] = '\0'; return rx_return; > > return '\0'; > bool SerialComms::WriteSerial(string data_out) < const char * tx_buffer = data_out.c_str(); if (serial_filestream != -1) < int wr = write(serial_filestream, tx_buffer, strlen(tx_buffer)); if (wr < 0)< printf("Error writing to Serial: %s \n", strerror(errno)); return 0; >else < printf("WroteSerial: %s, %d \n", tx_buffer, strlen(tx_buffer)); return 1; >>else < printf("Error writing to Serial filestream errors \n"); return 0; >return 1; > SerialComms::~SerialComms()
It works without a problem, and I receive back the expected data from the serial device. However, my main loop in the c++ program calls the WriteSerial function once a minute like so:
void Ping(SerialComms serialLine)
The first time the write is performed it doesn’t give an error, but it doesn’t seem to work, and the second write returns a bad file descriptor error. Also, it screws up reading from the port as well. Here is my program output:
WroteSerial: p,1,1; , 7 pinged at: Wed Dec 30 19:08:05 2015 Error writing to Serial: Bad file descriptor pinged at: Wed Dec 30 19:09:06 2015
I’m really not sure where to go from here so any suggestion would be appreciated. I’m pretty sure I’m missing something basic but I can’t see it at the moment.