Reputation: 13
I have to create a function that temporarily redirects the standard output to a file name "file", then executes the function f, then re-establishes the original standard output.
I'm having some trouble realizing it since I am clearly not good at shell and C. I know I have to use dup() and dup2() but I have no idea how to use them.
void redirect_to (void (*f)(void), char *file)
{
int fd = open(file, O_CREAT | O_TRUNC | O_WRONLY, 0644);
int old = dup(file);
dup2(fd, old);
f();
close(fd);
}
I am pretty sure I am doing all wrong tho but can't understand how to do it. Thank you very much..
Upvotes: 1
Views: 1425
Reputation: 13
I finally got the correct answer, so here is how to temporarily redirects the standard output to a file name "file", then executes the function f, then re-establishes the original standard output:
void redirect_stdout (void (*f)(void), char *file)
{
int fd = open(file, O_CREAT | O_TRUNC | O_WRONLY, 0644);
int savefd = dup(1);
dup2(fd, 1);
f();
dup2(savefd, 1);
close(fd);
close(savefd);
}
Upvotes: 0
Reputation: 12708
There are several ways in a program in which you can "redirect" your output to a file, execute a function and then restore the original output in the place.
First of all, you don't actually need to do any file descriptor tweaking, if you just rethinkg your programming task and change the function interface for the function you'll pass to your "redirecting" function:
RET_TYPE redirect_and_call_v1(RET_TYPE(*funct_to_be_called)(int descriptor_to_write_to), int fd_to_be_used_as_output)
{
return funct_to_be_called(fd_to_be_used_as_output);
}
You'll see from this example, that I have slighty modified your API, in order to pass the redirect_and_call_v1
function an already open descriptor (because this way you can use your own standard output as the redirection file descriptor, and you don't force it to be a file) and I have passed that precise descriptor, as a parameter to the funct_to_be_called
function, so it knows where to write things to.
In the second version, I'll respect your calling interface, assuming you cannot control where the function you are going to pass as a parameter will write to an open descriptor (which you must know) and you want it to save that descriptor for restoring it later, and then redirect it to the descriptor we want to be used.
/* do error checking */
#define CHECK_ERR(var, name, exit_code) do { \
if((var) < 0) { \
fprintf(stderr, "ERROR: " name ": %s\n", \
strerror(errno)); \
exit(exit_code); \
} \
} while(0)
RET_TYPE redirect_and_call_v2(
RET_TYPE(*funct_to_be_called)(void),
int fd_to_be_used_in_function_for_output,
int fd_funct_uses_to_write)
{
/* save a copy descriptor, so we can overwrite the fd the one
* function uses to write */
int saved_old_fd = dup(fd_funct_uses_to_write);
CHECK_ERR(saved_old_fd, "dup", 1);
/* now, we change the descriptor the function uses to write by the one we want
* it to be used for output */
int res = dup2(fd_to_be_used_in_function_for_output, fd_funct_uses_to_write);
CHECK_ERR(res, "dup2", 2);
/* now, we call the function */
RET_TYPE res2 = funct_to_be_called();
/* now restore descriptors to what they where. For this, we use the saved
* file descriptor */
res = dup2(saved_old_func, fd_funct_uses_to_write);
/* close the saved descriptor, as we dont need it anymore */
close(saved_old_func);
/* and finally, return the funct_to_be_called return value */
return res2;
}
A final complete example is shown below (a github repository of this solution is given here):
main.c
:/* main.c --- main program for the test of temporary redirection
* of a file descriptor.
* fputs(3) prints to stdout, but we are going to intersperse
* printf(3) calls, with puts(3) calls (with temporary
* redirection) to see that the temporary redirection actually
* works. All the output will be \n terminated, so we are sure
* that buffers are flushed out on each call.
*
* Author: Luis Colorado <[email protected]>
* Date: Sat Oct 12 13:28:54 EEST 2019
* Copyright: (C) LUIS COLORADO. All rights reserved.
* License: BSD.
*/
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "redirect.h"
/* this is our unbuffering simulation of the puts(3) function,
* as trouble will come if we don't consider the flushing of
* buffers properly, and I don't want to overcomplicate things
* with stdio buffering idiosyncracies. Function forcibly writes
* its output to standard output, so to make it use a different
* descriptor, we need to redirect standard output to a different
* place before call, and restore it later.*/
ssize_t output_function(const char *s)
{
/* ensure all output is flushed before returning back */
return write(1, s, strlen(s));
}
void do_usage(const char *progname)
{
fprintf(stderr,
"Usage: %s [ file ... ]\n"
"where 'file' is the output file to redirect output to.\n"
"\n"
"The program just uses a different string to output to both,\n"
"standard output and the indicated filename, to thow the workings\n"
"of output redirection. I have wrapped the call to fputs(3) in a\n"
"function in order to show that we need to call fflush to flush\n"
"the buffers out to the file descriptor, before returning, or the\n"
"wrong things will be written to the output descriptors. (this is\n"
"specially true for files, that do full buffering)\n",
progname);
}
int main(int argc, char **argv)
{
int i;
if (argc == 1) {
do_usage(argv[0]);
} else for (i = 1; i < argc; i++) {
char buffer[1024];
/* text we are going to write multiple times */
snprintf(buffer, sizeof buffer,
"%d: file = %s\n", i, argv[i]);
/* we call our output function first, to see output going
* to it's normal output descriptor */
output_function(buffer);
/* create a file we are going to write message into */
int fd_to_redirect_output = open(
argv[i],
O_WRONLY | O_CREAT | O_TRUNC,
0666);
/* call our function with the message and the redirection
* done before the call and restored after */
int res = redirect_and_call(output_function,
fd_to_redirect_output, /* descriptor to write
* output to */
1, /* stdout is file descriptor 1 */
buffer); /* write the string to the file. */
/* write another message to see redirection was restored
* properly */
output_function("done\n");
}
exit(EXIT_SUCCESS);
} /* main */
redirect.h
:/* redirect.h --- definitions for the module redirect.c
* Author: Luis Colorado <[email protected]>
* Date: Sat Oct 12 13:24:54 EEST 2019
* Copyright: (C) 2019 LUIS COLORADO. All rights reserved.
* License: BSD.
*/
#ifndef _REDIRECT_H
#define _REDIRECT_H
/* our redirect_and_call will return int, as fputs(3) returns
* int, and will need an extra parameter, so it can call
* fputs, and pass it the proper parameter. */
typedef ssize_t RET_TYPE;
RET_TYPE redirect_and_call(
RET_TYPE (*funct_to_be_called)(const char*s),
int fd_to_write_to,
int fd_funct_uses_as_output,
const char *parameter_for_the_call);
#endif /* _REDIRECT_H */
redirect.c
:/* redirect.c --- function to create a temporary redirect of a
* file descriptor in order to execute a function (passed as a
* parameter) with that descriptor redirected so it will output
* to the redirected desriptor, instead of to the one it used to.
* We'll use puts(3) as an example of a function that is
* hard wired to print into stdout (fd = 1) and to make it to
* write to a named file instead.
*
* Author: Luis Colorado <[email protected]>
* Date: Sat Oct 12 13:04:45 EEST 2019
* Copyright: (C) 2019 LUIS COLORADO. All rights reserved.
* License: BSD.
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "redirect.h"
#define F(_f) __FILE__":%d:%s: " _f, __LINE__, __func__
#define CHECK_ERR(var, status, name) do { \
if ((var) < 0) { \
fprintf(stderr, \
F("%s: %s\n"), \
name, strerror(errno)); \
exit(status); \
} \
} while(0)
RET_TYPE redirect_and_call(
RET_TYPE (*funct_to_be_called)(const char*s),
int fd_to_write_to,
int fd_funct_uses_as_output,
const char *parameter_for_the_call)
{
/* save a copy descriptor, so we can overwrite the fd the one
* function uses to write */
int saved_old_fd = dup(
fd_funct_uses_as_output);
CHECK_ERR(saved_old_fd, 1, "dup");
/* now, we change the descriptor the function uses to write
* by the one we want it to be used for output */
int res = dup2(
fd_to_write_to,
fd_funct_uses_as_output);
CHECK_ERR(res, 2, "dup2");
/* now, we call the function */
RET_TYPE res2 = funct_to_be_called(parameter_for_the_call);
/* now restore descriptors to what they where. For this, we
* use the saved file descriptor */
res = dup2(saved_old_fd, fd_funct_uses_as_output);
/* close the saved descriptor, as we dont need it anymore */
close(saved_old_fd);
/* and finally, return the funct_to_be_called return value */
return res2;
} /* redirect_and_call */
Makefile
:# Makefile --- Makefile for the test program.
# Author: Luis Colorado <[email protected]>
# Date: Sat Oct 12 13:43:03 EEST 2019
# Copyright: (C) 2019 LUIS COLORADO. All rights reserved.
# License: BSD.
targets = redirect
toclean = $(targets)
RM ?= rm -f
all: $(targets)
clean:
$(RM) $(toclean)
redirect_objs = main.o redirect.o
toclean += $(redirect_objs)
redirect: $(redirect_deps) $(redirect_objs)
$(CC) $(LDFLAGS) -o $@ $($@_objs) $($@_ldflags) $($@_ldlibs)
Upvotes: 2
Reputation: 1299
Your code is close, here is a complete working example:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
void some_func() {
printf("This gets written to file.\n");
fflush(stdout);
}
void redirect_stdout(void (*some_func)(void), char *file) {
printf("Preparing to redirect to file:\n");
fflush(stdout);
//sys has three streams (0)stdin (1)stdout (2)stderr
//use dup to store the stdout for restoration later:
int saved_stdout = dup(1);
int fw = open(file, O_CREAT | O_TRUNC | O_WRONLY, 0644);
//checks for open failure and cleans up resources / prints errors if so
if (fw < 0) {
printf("Failed to open %s.\n", file);
perror("Error: ");
if (close(saved_stdout) != 0) {
perror("Error: ");
}
return;
}
dup2(fw,1); //use 1 as it is the integer assigned to stdout
some_func();
if (close(fw) != 0) { //checks for bad file descriptor
perror("Error: ");
}
dup2(saved_stdout, 1);
if (close(saved_stdout) != 0) { //checks for bad file descriptor
perror("Error: ");
}
}
int main(void) {
char filename[] = "stdout_content.txt";
redirect_stdout(some_func, filename);
printf("Stdout now back to terminal output.\n");
return 0;
}
See the Linux man pages for dup, dup2, open, close, etc. Note I first tried this with freopen but was unable to figure out any way to restore stdout to the terminal, and there seem to be other methods using fork() etc. that are more involved. http://www.microhowto.info/howto/capture_the_output_of_a_child_process_in_c.html
Upvotes: 1