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)

Exiting a program with a relevant return value

In this recipe, we'll learn how to exit a C program with a relevant return value. We will look at two different ways to exit a program with a return value and how return fits together with the system from a broader perspective. We will also learn what some common return values mean.

Getting ready

For this recipe, we only need the GCC compiler and the Make tool.

How to do it…

We will write two different versions of a program here to show you two different methods of exiting. Let's get started:

  1. We'll start by writing the first version using return, which we have seen previously. But this time, we will use it to return from functions, all the way back to main() and eventually the parent process, which is the shell. Save the following program in a file called functions_ver1.c. All the return statements are highlighted in the following code:
    #include <stdio.h>
    int func1(void);
    int func2(void);
    int main(int argc, char *argv[])
    {
       printf("Inside main\n");
       printf("Calling function one\n");
       if (func1())
       {
          printf("Everything ok from function one\n");
          printf("Return with 0 from main - all ok\n");
          return 0;
       }
       else
       {
          printf("Caught an error from function one\n");
          printf("Return with 1 from main - error\n");
          return 1;
       }
       return 0; /* We shouldn't reach this, but 
                    just in case */
    }
    int func1(void)
    {
       printf("Inside function one\n");
       printf("Calling function two\n");
       if (func2())
       {
          printf("Everything ok from function two\n");
          return 1;
       }
       else
       {
          printf("Caught an error from function two\n");
          return 0;
       }
    }
    int func2(void)
    {
       printf("Inside function two\n");
       printf("Returning with 0 (error) from "
          "function two\n");
       return 0;
    }
  2. Now, compile it:
    $> gcc functions_ver1.c -o functions_ver1
  3. Then, run it. Try to follow along and see which functions call and return to which other functions:
    $> ./functions-ver1
    Inside main 
    Calling function one 
    Inside function one 
    Calling function two 
    Inside function two 
    Returning with 0 (error) from function two 
    Caught an error from function two 
    Caught an error from function one 
    Return with 1 from main – error
  4. Check the return value:
    $> echo $?
    1
  5. Now, we rewrite the preceding program to use exit() inside the functions instead. What will happen then is that as soon as exit() is called, the program will exit with the specified value. If exit() is called inside another function, that function will not return to main() first. Save the following program in a new file as functions_ver2.c. All the return and exit statements are highlighted in the following code:
    #include <stdio.h>
    #include <stdlib.h>
    int func1(void);
    int func2(void);
    int main(int argc, char *argv[])
    {
       printf("Inside main\n");
       printf("Calling function one\n");
       if (func1())
       {
          printf("Everything ok from function one\n");
          printf("Return with 0 from main - all ok\n");
          return 0;
       }
       else
       {
          printf("Caught an error from funtcion one\n");
          printf("Return with 1 from main - error\n");
          return 1;
       }
       return 0; /* We shouldn't reach this, but just 
                    in case */
    }
    int func1(void)
    {
       printf("Inside function one\n");
       printf("Calling function two\n");
       if (func2())
       {
          printf("Everything ok from function two\n");
          exit(0);
       }
       else
       {
          printf("Caught an error from function two\n");
          exit(1);
       }
    }
  6. Now, compile this version:
    $> gcc functions_ver2.c -o functions_ver2
  7. Then, run it and see what happens (and compare the output from the previous program):
    $> ./functions_ver2
    Inside main
    Calling function one
    Inside function one
    Calling function two
    Inside function two
    Returning with (error) from function two
  8. Finally, check the return value:
    $> echo $?
    1

How it works…

Notice that in C, 0 is regarded as false or error, while anything else is considered to be true (or correct). This is the opposite of the return values to the shell. This can be a bit confusing at first. However, as far as the shell is concerned, 0 is "all ok," while anything else indicates an error.

The difference between the two versions is how the functions and the entire program returns. In the first version, each function returns to the calling function—in the order they were called. In the second version, each function exits with the exit() function. This means that the program will exit directly and return the specified value to the shell. The second version isn't good practice; it's much better to return to the calling function. If someone else were to use your function in another program, and it suddenly exits the entire program, that would be a big surprise. That's not usually how we do it. However, I wanted to demonstrate the difference between exit() and return here.

I also wanted to demonstrate another point. Just as a function returns to its calling function with return, a program returns to its parent process (usually the shell) in the same way. So, in a way, programs in Linux are treated as functions in a program.

The following diagram shows how Bash calls the program (the upper arrow), which then starts in main(), which then calls the next function (the arrows to the right), and so on. The arrows returning on the left show how each function returns to the calling function, and then finally to Bash:

Figure 2.1 – Calling and returning

There's more…

There are a lot more return codes we can use. The most common ones are the ones we've seen here; 0 for ok and 1 for error. However, all other codes except 0 mean some form of error. Code 1 is a general error, while the other error codes are more specific. There isn't exactly a standard, but there are some commonly used codes. Some of the most common codes are as follows:

Figure 2.2 – Common error codes in Linux and other UNIX-like systems

Except for these codes, there are some additional ones listed at the end of /usr/include/sysexit.h. The codes listed in that file range from 64 to 78 and address errors such as data format error, service unavailable, I/O errors, and more.