Book Image

Linux System Programming Techniques

By : Jack-Benny Persson
Book Image

Linux System Programming Techniques

By: Jack-Benny Persson

Overview of this book

Linux is the world's most popular open source operating system (OS). Linux System Programming Techniques will enable you to extend the Linux OS with your own system programs and communicate with other programs on the system. The book begins by exploring the Linux filesystem, its basic commands, built-in manual pages, the GNU compiler collection (GCC), and Linux system calls. You'll then discover how to handle errors in your programs and will learn to catch errors and print relevant information about them. The book takes you through multiple recipes on how to read and write files on the system, using both streams and file descriptors. As you advance, you'll delve into forking, creating zombie processes, and daemons, along with recipes on how to handle daemons using systemd. After this, you'll find out how to create shared libraries and start exploring different types of interprocess communication (IPC). In the later chapters, recipes on how to write programs using POSIX threads and how to debug your programs using the GNU debugger (GDB) and Valgrind will also be covered. By the end of this Linux book, you will be able to develop your own system programs for Linux, including daemons, tools, clients, and filters.
Table of Contents (14 chapters)

Writing to stdout and stderr

In this recipe, we'll learn how to print text to both stdout and stderr in a C program. In the two previous recipes, we learned what stdout and stderr are, why they exist, and how to redirect them. Now, it's our turn to write correct programs that output error messages on standard error, and regular messages on standard output.

How to do it…

Follow these steps to learn how to write output to both stdout and stderr in a C program:

  1. Write the following code in a file called output.c and save it. In this program, we will write output using three different functions: printf(), fprintf(), and dprintf(). With fprintf(), we can specify a file stream such as stdout or stderr, while with dprintf(), we can specify the file descriptor (1 for stdout and 2 for stderr, just as we have seen previously):
    #define _POSIX_C_SOURCE 200809L
    #include <stdio.h>
    int main(void)
    {
       printf("A regular message on stdout\n");
       /* Using streams with fprintf() */
       fprintf(stdout, "Also a regular message on " 
         	 "stdout\n");
       fprintf(stderr, "An error message on stderr\n");
       /* Using file descriptors with dprintf().
        * This requires _POSIX_C_SOURCE 200809L 
        * (man 3 dprintf)*/
       dprintf(1, "A regular message, printed to "
          	  "fd 1\n");
       dprintf(2, "An error message, printed to "
          	   "fd 2\n");
       return 0;
    }
  2. Compile the program:
    $> gcc output.c -o output
  3. Run the program like you usually would:
    $> ./output 
    A regular message on stdout
    Also a regular message on stdout
    An error message on stderr
    A regular message, printed to fd 1
    An error message, printed to fd 2
  4. To prove that the regular messages are printed to stdout, we can send the error messages to /dev/null, a black hole in the Linux system. Doing this will only display the messages printed to stdout:
    $> ./output 2> /dev/null 
    A regular message on stdout
    Also a regular message on stdout
    A regular message, printed to fd 1
  5. Now, we will do the reverse; we will send the messages printed to stdout to /dev/null, showing only the error messages that are printed to stderr:
    $> ./output > /dev/null
    An error message on stderr
    An error message, printed to fd 2
  6. Finally, let's send all messages, from both stdout and stderr, to /dev/null. This will display nothing:
    $> ./output &> /dev/null

How it works…

The first example, where we used printf(), doesn't contain anything new or unique. All output printed with the regular printf() function is printed to stdout.

Then, we saw some new examples, including the two lines where we use fprintf(). That function, fprintf(), allows us to specify a file stream to print the text to. We will cover what a stream is later on in this book. But in short, a file stream is what we usually open when we want to read or write to a file in C using the standard library. And remember, everything is either a file or a process in Linux. When a program opens in Linux, three file streams are automatically opened—stdin, stdout, and stderr (assuming the program has included stdio.h).

Then, we looked at some examples of using dprintf(). This function allows us to specify a file descriptor to print to. We covered file descriptors in the previous recipes of this chapter, but we will discuss them in more depth later in this book. Three file descriptors are always open—0 (stdin), 1 (stdout), and 2 (stderr)—in every program we write on Linux. Here, we printed the regular message to file descriptor (fd for short) 1, and the error message to file descriptor 2.

To be correct in our code, we need to include the very first line (the #define line) for the sake of dprintf(). We can read all about it in the manual page (man 3 dprintf), under Feature Test Macro Requirements. The macro we define, _POSIX_C_SOURCE, is for POSIX standards and compatibility. We will cover this in more depth later in this book.

When we tested the program, we verified that the regular messages got printed to standard output by redirecting the error messages to a file called /dev/null, showing only the messages printed to standard output. Then, we did the reverse to verify that the error messages got printed to standard error.

The special file, /dev/null, acts as a black hole in Linux and other Unix systems. Everything we send to that file simply disappears. Try it out with ls / &> /dev/null, for example. No output will be displayed since everything is redirected to the black hole.

There's more…

I mentioned that three file streams are opened in a program, assuming it includes stdio.h, as well as three file descriptors. These three file descriptors are always opened, even if stdio.h is not included. If we were to include unistd.h, we could also use macro names for the three file descriptors.

The following table shows these file descriptors, their macro names, and file streams, which are handy for future reference:

Figure 2.3 – File descriptors and file streams in Linux

Figure 2.3 – File descriptors and file streams in Linux