I need to restart process using the common function restart(...) for all processes from my library. Client code should not do any extra work for restart.
I use execve() system call for restart. The example below suggests that the process may have file locks locks that are from the outside of scope of restart(...) function.
#include <sys/file.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define LOCKFILE "./test_exec_restart.lock"
extern char ** environ;
void restart(char ** argv)
{
char * new_argv[] = { argv[0], NULL };
int res = execve(new_argv[0], new_argv, environ);
fprintf(stderr, "execve() error: %d(%s)\n", errno, strerror(errno));
exit(-3);
}
int main(int argc, char** argv)
{
int do_exec = 0;
if (argc > 1 && strncmp(argv[1], "-e", 2) == 0)
do_exec = 1;
printf("do_exec: %d\n", do_exec);
FILE* file = fopen(LOCKFILE, "w+");
if (file == NULL)
{
fprintf(stderr, "Cannot open lockfile, error: %d(%s)\n", errno, strerror(errno));
return -1;
}
if (flock(fileno(file), LOCK_EX | LOCK_NB) < 0)
{
fprintf(stderr, "Cannot exclusive lock, error: %d(%s)\n", errno, strerror(errno));
return -2;
}
if (do_exec)
restart(argv);
if (flock(fileno(file), LOCK_UN) < 0)
{
fprintf(stderr, "Cannot unlock, error: %d(%s)\n", errno, strerror(errno));
return -4;
}
return 0;
}
This example gets lock error after restart:
$ gcc main.c -o main
$ ./main -e
do_exec: 1
do_exec: 0
Cannot exclusive lock, error: 11(Resource temporarily unavailable)
What should be done for get exclusive lock after exec() so that the client code does not do any extra work for this?
The reason the new execution can't lock the file is that it already has the file open and locked. When you execve all open file descriptors are preserved. You're options are:
Close the descriptor on exec: Either open with O_CLOEXEC, apply O_CLOEXEC with fdctl, or close manually before exec - the downside of this is the lock is released between the close and the new flock
Pass sufficient information to the new process to own the FD: probably passing the FD number in an argument to the new execution.
Switch to process-level locking: F_SETLK etc from https://man7.org/linux/man-pages/man2/fcntl.2.html - I think those will allow the new execution to relock the file.
You can mark the Lock filedescriptor O_CLOEXEC, then it will be closed by the execve, and can be reopened by the new process.
According to https://www.gnu.org/software/libc/manual/html_node/Opening-Streams.html you can add "e" to the end of the mode for the fopen to achieve this.
Restarted process can't exclusuve lock because it inherited opened files from first process, and trying lock inherited locked file via inherited descriptor that already have lock.
man execve:
By default, file descriptors remain open across an execve().
File descriptors that are marked close-on-exec are closed; see
the description of FD_CLOEXEC in fcntl(2). (If a file
descriptor is closed, this will cause the release of all
record locks obtained on the underlying file by this process.
See fcntl(2) for details.) POSIX.1 says that if file
descriptors 0, 1, and 2 would otherwise be closed after a
successful execve(), and the process would gain privilege
because the set-user-ID or set-group-ID mode bit was set on
the executed file, then the system may open an unspecified
file for each of these file descriptors. As a general
principle, no portable program, whether privileged or not, can
assume that these three file descriptors will remain closed
across an execve().
My solution is close all files, except stdin, stdout and stderr:
...
int close_all_files(int lowfd)
{
DIR* dir = opendir("/proc/self/fd");
if (dir == NULL)
{
fprintf(stderr, "Cannot open directory: '/proc/self/fd', error: %d(%s)\n", errno, strerror(errno));
return -1;
}
struct dirent* ent = NULL;
while ((ent = readdir(dir)) != NULL)
{
int fd = atoi(ent->d_name);
if (fd > lowfd)
close(fd);
}
closedir(dir);
return 0;
}
void restart(char ** argv)
{
close_all_files(fileno(stderr));
char * new_argv[] = { argv[0], NULL };
int res = execve(new_argv[0], new_argv, environ);
fprintf(stderr, "execve() error: %d(%s)\n", errno, strerror(errno));
exit(-3);
}
...
It is also possible using fdwalk(...) function, but it is possibly not available on all Linux/UNIX systems.
REM: This is bad solution, because active thread that handled signal is unspecified, and all files may be closed during writes in them. This will result in a loss of data consistency.
Related
I was trying something like this but got stuck and don't know in which direction to proceed. I even tried using fork() and then assigning the task separately to child and parent but the redirection in that case in not working as intended.
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
execlp("cat","<","f1.txt",">","f2.txt",NULL);
return 0;
}
In Linux and other POSIXy operating systems, standard input corresponds to file descriptor 0 (STDIN_FILENO), standard output to file descriptor 1 (STDOUT_FILENO), and standard error to file descriptor 2 (STDERR_FILENO).
Standard file handles stdin, stdout, and stderr are the standard C abstraction, and in Linux are implemented on top of those file descriptors.
To redirect standard input, output, or error, first you need to get an open file descriptor to whatever you want to redirect from/to. In the case of files, you do this via the open() function, which returns the file descriptor number. Then, you use the dup2() function to duplicate (copy) that to the descriptor you want.
Consider the following example.c:
// SPDX-License-Identifier: CC0-1.0
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
/* Duplicate oldfd to newfd, and close oldfd.
In error cases, tries to close both descriptors.
Returns 0 if success, error code (with errno set) otherwise.
*/
static inline int move_descriptor(int oldfd, int newfd)
{
if (oldfd == -1 || newfd == -1) {
if (oldfd != -1)
close(oldfd);
if (newfd != -1)
close(newfd);
return errno = EBADF;
}
if (oldfd == newfd)
return 0;
if (dup2(oldfd, newfd) == -1) {
const int saved_errno = errno;
close(oldfd);
close(newfd);
return errno = saved_errno;
}
if (close(oldfd) == -1) {
const int saved_errno = errno;
close(newfd);
return errno = saved_errno;
}
return 0;
}
/* Write a message to standard error, keeping errno unchanged.
This is async-signal safe.
Returns 0 if success, error code otherwise.
*/
static inline int wrerr(const char *msg)
{
const char *end = (msg) ? msg + strlen(msg) : msg;
if (end == msg)
return 0;
const int saved_errno = errno;
while (msg < end) {
ssize_t n = write(STDERR_FILENO, msg, (size_t)(end - msg));
if (n > 0) {
msg += n;
} else
if (n != -1) {
errno = saved_errno;
return EIO;
} else
if (errno != EINTR) {
const int retval = errno;
errno = saved_errno;
return retval;
}
}
errno = saved_errno;
return 0;
}
static inline void errormessage(const char *name, const char *cause)
{
wrerr(name);
wrerr(": ");
wrerr(cause);
wrerr(".\n");
}
int main(int argc, char *argv[])
{
int fd;
if (argc < 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
const char *arg0 = (argc > 0 && argv && argv[0] && argv[0][0]) ? argv[0] : "(this)";
wrerr("\n");
wrerr("Usage: "); wrerr(arg0); wrerr(" [ -h | --help ]\n");
wrerr(" "); wrerr(arg0); wrerr(" INPUT OUTPUT COMMAND [ ARGUMENTS ... ]\n");
wrerr("\n");
return EXIT_FAILURE;
}
fd = open(argv[1], O_RDONLY | O_NOCTTY);
if (fd == -1) {
errormessage(argv[1], strerror(errno));
return EXIT_FAILURE;
}
if (move_descriptor(fd, STDIN_FILENO)) {
errormessage(argv[1], strerror(errno));
return EXIT_FAILURE;
}
fd = open(argv[2], O_WRONLY | O_CREAT, 0666);
if (fd == -1) {
errormessage(argv[2], strerror(errno));
return EXIT_FAILURE;
}
if (move_descriptor(fd, STDOUT_FILENO)) {
errormessage(argv[2], strerror(errno));
return EXIT_FAILURE;
}
if (strchr(argv[3], '/'))
execv(argv[3], (char *const *)(argv + 3));
else
execvp(argv[3], (char *const *)(argv + 3));
errormessage(argv[3], strerror(errno));
return EXIT_FAILURE;
}
The move_descriptor() is just a wrapper around dup2() and close(). I included it to show how to do the descriptor moving (copying and closing the old one) safely; with sufficient error checking.
The wrerr(msg) function is analogous to fputs(msg, stderr), except that it uses the file descriptor interface (write()) directly, bypassing the C stderr stream abstraction completely. It is also async-signal safe*, meaning you can use it inside signal handlers.
*: Technically, one could argue whether strlen() is async-signal safe or not. In Linux using glibc, newlibc, or avr-libc, it is.
Many Linux/Unix/POSIXy error messages use format "filename: Error message." Since the wrerr() function takes only one parameter, I included the errormessage(filename, message) helper function to print such error messages. This kind of splitting commonly used tasks to helper functions makes the code easier to read, and easier to maintain too.
The program itself takes at least four command-line arguments. (The first argument, argv[0], is the command itself. The first parameter is argv[1]. For example, if you compile and ran this as ./example arg1 arg2 arg3, then argv[0]=="./example", argv[1]=="arg1", argv[2]=="arg2", and argv[3]=="arg3". In Linux and POSIXy systems, argv[argc] == NULL, so we can use the argv array directly in execv() and execvp() and related functions.
If there are fewer than four command-line parameters, or if argv[1] matches "-h" or "--h", we print usage and exit.
Otherwise, the first parameter (argv[1]) names the file we redirect input from (and it must exist), and the second parameter (argv[2]) the file we redirect output to (which we'll create if it does not exist yet).
The O_NOCTTY flag may look confusing at first, but I included it, because it is so common when redirecting input from file-like objects. It basically means that even if the pathname refers to a terminal, don't do any terminal and session related magic when opening it: "if it is a terminal, and we don't happen to have a controlling terminal, don't make it our controlling terminal". (It only affects programs run in a new session (via setsid) or by services like cron, udev, et cetera, since programs you normally run from a terminal have that terminal as their controlling terminal. The main thing about terminals and sessions is that if the controlling terminal gets closed, each process having that terminal as their controlling terminal will receive a hangup (SIGHUP) signal.)
When opening the file we redirect output to, we use O_CREAT flag, and add an extra parameter, the file access mode. The leading zero means that 0666 is an octal constant, i.e. base-8, and refers to decimal value 6·82 + 6·81 + 6·80 = 438. It is the standard value that you see most often used. It is modified (by the kernel) by the current umask (whose value you can see in the shell by running umask). It is written in octal because then the third digit from right specifies the owner (user) rights, second from right the group rights, and the rightmost the rights for everyone else; 1 being read access, 2 being write access, and 4 being execute (for files) or pass through/work in (for directories). (Each file has an owner (user) and group in Linux, as they do in all Unix and POSIXy systems.)
Whenever open() flags include O_CREAT, the additional access mode value must be supplied. It will almost always be 0666, except for some rare cases where you want to use a more restrictive value; but the general rule is to use 0666 and let the user make it more restrictive if they want by modifying their umask: that is what just about all utilities do anyway.
The third command line parameter (fourth argument, argv[3]), contains the name or path to the executable we'll run. If it contains a slash (/), we assume it is a pathname reference, and use execv(). If it does not contain a slash, we assume it is a name, and use execvp(), which uses the PATH environment variable to look for an executable with that name.
If the exec succeeds, the process is replaced with the new program; the execution ends at the exec.
If the exec fails for any reason, both execv() and execvp() will set errno. It makes sense to print the command (without any arguments, if there were any), and the error message, and exit with failure in that case.
AFAIK, redirection is a shell feature.
I don't see what you are trying to do. But to achieve what you are trying, you need to run
sh -c 'cat < f1.txt > f2.txt'
in the exec format. (I am not familiar with the exec format in C)
You can use bash instead of sh. The -c flag takes a string argument — called command string, and executes it.
My guess of the exec format would be:
execlp( "sh", "-c", " 'cat < f1.txt > f2.txt' ", NULL);
Note the third argument to the function is enclosed in two sets of quotes " '...' ". This is a requirement. Else the shell's space splitting will break it and pass only cat as the command string.
When my program starts, it just creates a fifo and opens it, after that I just want to output some information to the screen, however, nothing gets printed out. Here's a snippet of my code:
void listen(const server_config_t* server_conf)
{
// Create FIFO
if (mkfifo(SERVER_FIFO_PATH, 0660) == -1) {
fprintf(stdout, "server FIFO not created as it already exists. continuing...\n");
}
// Open FIFO (for reading)
int fd;
if ((fd = open(SERVER_FIFO_PATH, O_RDONLY)) == -1) {
// fprintf(stderr, "error: could not open server FIFO\n");
perror("FIFO");
exit(1);
}
// Open dummy FIFO (for writing, prevent busy waiting)
// TODO: find way to wait without another file descriptor?
int fd_dummy;
if ((fd_dummy = open(SERVER_FIFO_PATH, O_WRONLY)) == -1) {
perror("DUMMY FIFO");
exit(1);
}
// TODO: this should print immediately after starting,
// but doesn't for some reason
fprintf(stdout, "server listening... %d %s\n", server_conf->num_threads,
server_conf->password);
fflush(stdout);
.
.
.
}
Here's my output:
I've tried commenting out the fifo creation and opening, and when I do that the message gets printed correctly to the screen.
Opening a FIFO normally blocks until the other end is opened as well, see http://man7.org/linux/man-pages/man7/fifo.7.html. So your program probably waits in open(SERVER_FIFO_PATH, O_RDONLY) and does not reach any other fprintf or perror.
Your attempt to open the FIFO for reading first and then for writing does not work because the first open does not return.
You should be able to see this when you step through your program using a debugger.
BTW: When mkfifo returns -1 you should check if errno is EEXIST. There could be other errors that would also result in return value -1, see https://linux.die.net/man/3/mkfifo
As you can see from your output, there is blocking. That is, your current process cannot go on until the other end of the FIFO is opened for write. You should glance at the man page.
As to your error, there are two cases maybe the directory into which you want to place the FIFO doesn't permit to do that. Second case may be due to a system error. To overcome the issue, you need to change your fprintf as following.
#include <string.h>
#include <stdlib.h>
..
..
fprintf(stderr, "server FIFO not created as it already exists. Error: %s\n", strerror(errno));
exit(EXIT_FAILURE);
I have two file open in two different processes. There's a pipe connecting the two. Is it possible to write directly from one file to another? Especially if the process reading doesn't know the size of the file it's trying to read?
I was hoping to do something like this
#define length 100
int main(){
int frk = fork();
int pip[2];
pipe(pip);
if (frk==0){ //child
FILE* fp fopen("file1", "r");
write(pip[1],fp,length);
}
else {
FILE* fp fopen("file2", "w");
read(pip[0],fp,length);
}
Is it possible to write directly from one file to another?
C does not provide any mechanism for that, and it seems like it would require specialized hardware support. The standard I/O paradigm is that data get read from their source into memory or written from memory to their destination. That pesky "memory" in the middle means copying from one file to another cannot be direct.
Of course, you can write a function or program that performs such a copy, hiding the details from you. This is what the cp command does, after all, but the C standard library does not contain a function for that purpose.
Especially if the process reading doesn't know the size of the file it's trying to read?
That bit isn't very important. One simply reads and then writes (only) what one has read, repeating until there is nothing more to read. "Nothing more to read" means that a read attempt indicates by its return value that the end of the file has been reached.
If you want one process to read one file and the other to write that data to another file, using a pipe to convey data between the two, then you need both processes to implement that pattern. One reads from the source file and writes to the pipe, and the other reads from the pipe and writes to the destination file.
Special note: for the process reading from the pipe to detect EOF on that pipe, the other end has to be closed, in both processes. After the fork, each process can and should close the pipe end that it doesn't intend to use. The one using the write end then closes that end when it has nothing more to write to it.
In other unix systems, like BSD, there's a call to connect directly two file descriptors to do what you want, but don't know if there's a system call to do that in linux. Anywya, this cannot be done with FILE * descriptors, as these are the instance of a buffered file used by <stdio.h> library to represent a file. You can get the file descriptor (as the system knows it) of a FILE * instance by a call to the getfd(3) function call.
The semantics you are trying to get from the system are quite elaborate, as you want something to pass directly the data from one file descriptor to another, without intervention of any process (directly in the kernel), and the kernel needs for that a pool of threads to do the work of copying directly from the read calls to the write ones.
The old way of doing this is to create a thread that makes the work of reading from one file descriptor (not a FILE * pointer) and write to the other.
Another thing to comment is that the pipe(2) system call gives you two connected descriptors, that allow you to read(2) in one (the 0 index) what is write(2)n in the second (the 1 index). If you fork(2) a second process, and you do the pipe(2) call on both, you will have two pipes (with two descriptors each), one in each process, with no relationship between them. You will be able only to communicate each process with itself, but not with the other (which doesn't know anything about the other process' pipe descriptors) so no communication between them will be possible.
Next is a complete example of what you try to do:
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#define length 100
#define FMT(fmt) "pid=%d:"__FILE__":%d:%s: " fmt, getpid(), __LINE__, __func__
#define ERR(fmt, ...) do { \
fprintf(stderr, \
FMT(fmt ": %s (errno = %d)\n"), \
##__VA_ARGS__, \
strerror(errno), errno); \
exit(1); \
} while(0)
void copy(int fdi, int fdo)
{
unsigned char buffer[length];
ssize_t res, nread;
while((nread = res = read(fdi, buffer, sizeof buffer)) > 0) {
res = write(fdo, buffer, nread);
if (res < 0) ERR("write");
} /* while */
if (res < 0) ERR("read");
} /* copy */
int main()
{
int pip[2];
int res;
res = pipe(pip);
if (res < 0) ERR("pipe");
char *filename;
switch (res = fork()) {
case -1: /* error */
ERR("fork");
case 0: /* child */
filename = "file1";
res = open(filename, O_RDONLY);
if (res < 0) ERR("open \"%s\"", filename);
close(pip[0]);
copy(res, pip[1]);
break;
default: /* parent, we got the child's pid in res */
filename = "file2";
res = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0666);
if (res < 0) ERR("open \"%s\"", filename);
close(pip[1]);
copy(pip[0], res);
int status;
res = wait(&status); /* wait for the child to finish */
if (res < 0) ERR("wait");
fprintf(stderr,
FMT("The child %d finished with exit code %d\n"),
res,
status);
break;
} /* switch */
exit(0);
} /* main */
Throughout my years as a C programmer, I've always been confused about the standard stream file descriptors. Some places, like Wikipedia[1], say:
In the C programming language, the standard input, output, and error streams are attached to the existing Unix file descriptors 0, 1 and 2 respectively.
This is backed up by unistd.h:
/* Standard file descriptors. */
#define STDIN_FILENO 0 /* Standard input. */
#define STDOUT_FILENO 1 /* Standard output. */
#define STDERR_FILENO 2 /* Standard error output. */
However, this code (on any system):
write(0, "Hello, World!\n", 14);
Will print Hello, World! (and a newline) to STDOUT. This is odd because STDOUT's file descriptor is supposed to be 1. write-ing to file descriptor 1
also prints to STDOUT.
Performing an ioctl on file descriptor 0 changes standard input[2], and on file descriptor 1 changes standard output. However, performing termios functions on either 0 or 1 changes standard input[3][4].
I'm very confused about the behavior of file descriptors 1 and 0. Does anyone know why:
writeing to 1 or 0 writes to standard output?
Performing ioctl on 1 modifies standard output and on 0 modifies standard input, but performing tcsetattr/tcgetattr on either 1 or 0 works for standard input?
I guess it is because in my Linux, both 0 and 1 are by default opened with read/write to the /dev/tty which is the controlling terminal of the process. So indeed it is possible to even read from stdout.
However this breaks as soon as you pipe something in or out:
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
int main() {
errno = 0;
write(0, "Hello world!\n", 14);
perror("write");
}
and run with
% ./a.out
Hello world!
write: Success
% echo | ./a.out
write: Bad file descriptor
termios functions always work on the actual underlying terminal object, so it doesn't matter whether 0 or 1 is used for as long as it is opened to a tty.
Let's start by reviewing some of the key concepts involved:
File description
In the operating system kernel, each file, pipe endpoint, socket endpoint, open device node, and so on, has a file description. The kernel uses these to keep track of the position in the file, the flags (read, write, append, close-on-exec), record locks, and so on.
The file descriptions are internal to the kernel, and do not belong to any process in particular (in typical implementations).
File descriptor
From the process viewpoint, file descriptors are integers that identify open files, pipes, sockets, FIFOs, or devices.
The operating system kernel keeps a table of descriptors for each process. The file descriptor used by the process is simply an index to this table.
The entries to in the file descriptor table refer to a kernel file description.
Whenever a process uses dup() or dup2() to duplicate a file descriptor, the kernel only duplicates the entry in the file descriptor table for that process; it does not duplicate the file description it keeps to itself.
When a process forks, the child process gets its own file descriptor table, but the entries still point to the exact same kernel file descriptions. (This is essentially a shallow copy, will all file descriptor table entries being references to file descriptions. The references are copied; the referred to targets remain the same.)
When a process sends a file descriptor to another process via an Unix Domain socket ancillary message, the kernel actually allocates a new descriptor on the receiver, and copies the file description the transferred descriptor refers to.
It all works very well, although it is a bit confusing that "file descriptor" and "file description" are so similar.
What does all that have to do with the effects the OP is seeing?
Whenever new processes are created, it is common to open the target device, pipe, or socket, and dup2() the descriptor to standard input, standard output, and standard error. This leads to all three standard descriptors referring to the same file description, and thus whatever operation is valid using one file descriptor, is valid using the other file descriptors, too.
This is most common when running programs on the console, as then the three descriptors all definitely refer to the same file description; and that file description describes the slave end of a pseudoterminal character device.
Consider the following program, run.c:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
static void wrerrp(const char *p, const char *q)
{
while (p < q) {
ssize_t n = write(STDERR_FILENO, p, (size_t)(q - p));
if (n > 0)
p += n;
else
return;
}
}
static inline void wrerr(const char *s)
{
if (s)
wrerrp(s, s + strlen(s));
}
int main(int argc, char *argv[])
{
int fd;
if (argc < 3) {
wrerr("\nUsage: ");
wrerr(argv[0]);
wrerr(" FILE-OR-DEVICE COMMAND [ ARGS ... ]\n\n");
return 127;
}
fd = open(argv[1], O_RDWR | O_CREAT, 0666);
if (fd == -1) {
const char *msg = strerror(errno);
wrerr(argv[1]);
wrerr(": Cannot open file: ");
wrerr(msg);
wrerr(".\n");
return 127;
}
if (dup2(fd, STDIN_FILENO) != STDIN_FILENO ||
dup2(fd, STDOUT_FILENO) != STDOUT_FILENO) {
const char *msg = strerror(errno);
wrerr("Cannot duplicate file descriptors: ");
wrerr(msg);
wrerr(".\n");
return 126;
}
if (dup2(fd, STDERR_FILENO) != STDERR_FILENO) {
/* We might not have standard error anymore.. */
return 126;
}
/* Close fd, since it is no longer needed. */
if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO)
close(fd);
/* Execute the command. */
if (strchr(argv[2], '/'))
execv(argv[2], argv + 2); /* Command has /, so it is a path */
else
execvp(argv[2], argv + 2); /* command has no /, so it is a filename */
/* Whoops; failed. But we have no stderr left.. */
return 125;
}
It takes two or more parameters. The first parameter is a file or device, and the second is the command, with the rest of the parameters supplied to the command. The command is run, with all three standard descriptors redirected to the file or device named in the first parameter. You can compile the above with gcc using e.g.
gcc -Wall -O2 run.c -o run
Let's write a small tester utility, report.c:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
int main(int argc, char *argv[])
{
char buffer[16] = { "\n" };
ssize_t result;
FILE *out;
if (argc != 2) {
fprintf(stderr, "\nUsage: %s FILENAME\n\n", argv[0]);
return EXIT_FAILURE;
}
out = fopen(argv[1], "w");
if (!out)
return EXIT_FAILURE;
result = write(STDIN_FILENO, buffer, 1);
if (result == -1) {
const int err = errno;
fprintf(out, "write(STDIN_FILENO, buffer, 1) = -1, errno = %d (%s).\n", err, strerror(err));
} else {
fprintf(out, "write(STDIN_FILENO, buffer, 1) = %zd%s\n", result, (result == 1) ? ", success" : "");
}
result = read(STDOUT_FILENO, buffer, 1);
if (result == -1) {
const int err = errno;
fprintf(out, "read(STDOUT_FILENO, buffer, 1) = -1, errno = %d (%s).\n", err, strerror(err));
} else {
fprintf(out, "read(STDOUT_FILENO, buffer, 1) = %zd%s\n", result, (result == 1) ? ", success" : "");
}
result = read(STDERR_FILENO, buffer, 1);
if (result == -1) {
const int err = errno;
fprintf(out, "read(STDERR_FILENO, buffer, 1) = -1, errno = %d (%s).\n", err, strerror(err));
} else {
fprintf(out, "read(STDERR_FILENO, buffer, 1) = %zd%s\n", result, (result == 1) ? ", success" : "");
}
if (ferror(out))
return EXIT_FAILURE;
if (fclose(out))
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
It takes exactly one parameter, a file or device to write to, to report whether writing to standard input, and reading from standard output and error work. (We can normally use $(tty) in Bash and POSIX shells, to refer to the actual terminal device, so that the report is visible on the terminal.) Compile this one using e.g.
gcc -Wall -O2 report.c -o report
Now, we can check some devices:
./run /dev/null ./report $(tty)
./run /dev/zero ./report $(tty)
./run /dev/urandom ./report $(tty)
or on whatever we wish. On my machine, when I run this on a file, say
./run some-file ./report $(tty)
writing to standard input, and reading from standard output and standard error all work -- which is as expected, as the file descriptors refer to the same, readable and writable, file description.
The conclusion, after playing with the above, is that there is no strange behaviour here at all. It all behaves exactly as one would expect, if file descriptors as used by processes are simply references to operating system internal file descriptions, and standard input, output, and error descriptors are duplicates of each other.
I have been looking int the man 3 tcgetattr (as I want to change the terminal settings in a program) and found this.
int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_actions,
const struct termios *termios_p);
Question:
I would like to know what fd should mean? (it seems to be stdin, yet I do not understand why)?
Background
My comprehension is that terminal is input and output together, as my understanding was that a /dev/tty or /dev/pty yields stdin, stdout and stderr together.
fd stands for file descriptor, which is a reference to an OS file object. Because it is a reference, multiple different file descriptors may refer to the same file object.
stdin, stdout, and stderr are FILE * objects -- actual pointers to stdio FILE datastructures. You can get the file descriptor that refers to the underlying OS object with the fileno function.
So there's two levels of indirection going on here. The FILE * could all refer to the same FILE, but they don't; there are 3 separate FILE objects for stdin, stdout, and stderr. These FILE objects each contain a file descriptor, normally 0, 1 and 2 (I say normally -- the OS/lib sets them up this way and they'll only change if you explicitly change them in your program). The 3 file descriptors will then normally all refer to the same underlying OS object, which is a single terminal object.
Since there's (generally) only one terminal, and all these file descriptors (usually) refer to it, it doesn't matter which fd (0, 1, or 2) you use as the first argument to tcsetaddr.
Note that it is possible for these fds to refer to different objects -- if you start your program with redirections (< or > in the shell) then one or more of them will refer to some other file object and not the terminal.
To simplify Thomas Dickey's and Chris Dodd's answers, the typical code to select which descriptor is used to refer to the terminal is
int ttyfd;
/* Check standard error, output, and input, in that order. */
if (isatty(fileno(stderr)))
ttyfd = fileno(stderr);
else
if (isatty(fileno(stdout)))
ttyfd = fileno(stdout);
else
if (isatty(fileno(stdin)))
ttyfd = fileno(stdin);
else
ttyfd = -1; /* No terminal; redirecting to/from files. */
If your application insists on having access to the controlling terminal (the terminal the user used to execute this process), if there is one, you can use the following new_terminal_descriptor() function. For simplicity, I'll embed it in an example program:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
int new_terminal_descriptor(void)
{
/* Technically, the size of this buffer should be
* MAX( L_ctermid + 1, sysconf(_SC_TTY_NAME_MAX) )
* but 256 is a safe size in practice. */
char buffer[256], *path;
int fd;
if (isatty(fileno(stderr)))
if (!ttyname_r(fileno(stderr), buffer, sizeof buffer)) {
do {
fd = open(path, O_RDWR | O_NOCTTY);
} while (fd == -1 && errno == EINTR);
if (fd != -1)
return fd;
}
if (isatty(fileno(stdout)))
if (!ttyname_r(fileno(stdout), buffer, sizeof buffer)) {
do {
fd = open(path, O_RDWR | O_NOCTTY);
} while (fd == -1 && errno == EINTR);
if (fd != -1)
return fd;
}
if (isatty(fileno(stdin)))
if (!ttyname_r(fileno(stdin), buffer, sizeof buffer)) {
do {
fd = open(path, O_RDWR | O_NOCTTY);
} while (fd == -1 && errno == EINTR);
if (fd != -1)
return fd;
}
buffer[0] = '\0';
path = ctermid(buffer);
if (path && *path) {
do {
fd = open(path, O_RDWR | O_NOCTTY);
} while (fd == -1 && errno == EINTR);
if (fd != -1)
return fd;
}
/* No terminal. */
errno = ENOTTY;
return -1;
}
static void wrstr(const int fd, const char *const msg)
{
const char *p = msg;
const char *const q = msg + ((msg) ? strlen(msg) : 0);
while (p < q) {
ssize_t n = write(fd, p, (size_t)(q - p));
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1)
return;
else
if (errno != EINTR)
return;
}
}
int main(void)
{
int ttyfd;
ttyfd = new_terminal_descriptor();
if (ttyfd == -1)
return EXIT_FAILURE;
/* Let's close the standard streams,
* just to show we're not using them
* for anything anymore. */
fclose(stdin);
fclose(stdout);
fclose(stderr);
/* Print a hello message directly to the terminal. */
wrstr(ttyfd, "\033[1;32mHello!\033[0m\n");
return EXIT_SUCCESS;
}
The wrstr() function is just a helper function, that immediately writes the specified string to the specified file descriptor, without buffering. The string contains ANSI color codes, so that if successful, it will print a light green Hello! to the terminal, even if the standard streams were closed.
If you save the above as example.c, you can compile it using e.g.
gcc -Wall -Wextra -O2 example.c -o example
and run using
./example
Because the new_terminal_descriptor() uses ctermid() function to obtain the name (path) to the controlling terminal as a last resort -- this is not common, but I wanted to show here it is easy to do if you decide it is necessary --, it will print the hello message to the terminal even when all streams are redirected:
./example </dev/null >/dev/null 2>/dev/null
Finally, in case you are wondering, none of this is "special". I am not talking about the console terminal, which is the text-based console interface many Linux distributions provide as an alternative to the graphical environment, and the only local interface most Linux servers provide. All of the above uses just normal POSIX pseudoterminal interfaces, and will work just fine using e.g. xterm or any other normal terminal emulator (or Linux console), in all POSIXy systems -- Linux, Mac OS X, and the BSD variants.
Agreeing with #chris-dodd that the file descriptors corresponding to the streams stdin, stdout and stderr usually refer to the same terminal, some points are needed for the original question:
the fd parameter (file descriptor) for tcgetattr and tcsetattr has to be for a terminal.
you could obtain this for a stream using fileno, e.g., fileno(stdin).
POSIX defines constants for the default assignment of file descriptors to stdin, stdout and stderr as STDIN_FILENO, STDOUT_FILENO and STDERR_FILENO. However, it is possible to reopen any of the streams (or use dup or dup2) and alter the actual file descriptor.
while you can obtain a file descriptor for a stream, if you are doing anything interesting with the terminal attributes, that can interfere with the buffering used for streams. If you have to mix the two (file descriptor and stream), do the changes to terminal attributes before reading or writing the stream.
you can also get a file descriptor using an open on the terminal device. This is useful if the streams are redirected, and your application has to work with the terminal. Password prompts do this.
the terminal device can be read from the program tty (even if stdin, etc., are redirected).
programs can check a file descriptor using isatty to see if it is a terminal. If a stream is redirected to a file or pipe, it is not a terminal.
Further reading:
11. General Terminal Interface (POSIX)
What is the difference between stdin and STDIN_FILENO?
By way of experiment I have found myself the following answer:
Each of the triple stderr,stdout,stdin was able to be used to change the terminal settings via the tcsetattr(fd....) function. Once a change was effectuated reading tcgsetattr(stdin....),tcgsetattr(stdout....), and also tcgsetattr(sterr....) returned the same content in the struct termios.h which could be verified via memcmp(&termios_stdin,&termios_stdout,sizeof(struct termios)) == 0
also maybe the man page stated somewhat indirectly this
tcgetattr() gets the parameters associated with the object referred by
fd and stores them in the termios structure referenced by termios_p.
This function may be invoked from a background process; however, the
terminal attributes may be subsequently changed by a foreground
process.
about fd hence the object referred by fd is always the same terminal