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)

Return values and how to read them

Return values are a big deal in Linux and other Unix and Unix-like systems. They are a big deal in C programming as well. Most functions in C return some value with return. It's that same return statement we use to return a value from main() to the shell. The original Unix operating system and the C programming language came around at the same time and from the same place. As soon as the C language was completed in the early 1970s, Unix was rewritten in C. Previously, it was written in assembler only. And hence, C and Unix fit together tightly.

The reason why return values are so crucial in Linux is that we can build shell scripts. Those shell scripts use other programs and, hopefully, our programs, as its parts. For the shell script to be able to check whether a program has succeeded or not, it reads the return value of that program.

In this recipe, we will write a program that tells the user if a file or directory exists or not.

Getting ready

It's recommended that you use Bash for this recipe. I can't guarantee compatibility with other shells.

How to do it…

In this recipe, we will write a small shell script that demonstrates the purpose of the return values, how to read them, and how to interpret them. Let's get started:

  1. Before we write the code, we must investigate what return values the program uses that we will use in our script. Execute the following commands, and make a note of the return values we get. The test command is a small utility that tests certain conditions. In this example, we'll use it to determine if a file or directory exists. The -e option stands for exists. The test command doesn't give us any output; it just exits with a return value:
    $> test -e /
    $> echo $?
    0
    $> test -e /asdfasdf
    $> echo $?
    1
  2. Now that we know what return values the test program gives us (0 when the file or directory exists, otherwise 1), we can move on and write our script. Write the following code in a file and save it as exist.sh. You can also download it from https://github.com/PacktPublishing/Linux-System-Programming-Techniques/blob/master/ch2/exist.sh. The shell script uses the test command to determine whether the specified file or directory exists:
    #!/bin/bash 
    # Check if the user supplied exactly one argument 
    if [ "$#" -ne 1 ]; then 
        echo "You must supply exactly one argument." 
        echo "Example: $0 /etc" 
        exit 1 # Return with value 1 
    fi 
    # Check if the file/directory exists 
    test -e "$1" # Perform the actual test
    if [ "$?" -eq 0 ]; then 
        echo "File or directory exists" 
    elif [ "$?" -eq 1 ]; then 
        echo "File or directory does not exist" 
        exit 3 # Return with a special code so other
               # programs can use the value to see if a 
               # file dosen't exist
    else 
        echo "Unknown return value from test..."
        exit 1 # Unknown error occured, so exit with 1
    fi 
    exit 0 # If the file or directory exists, we exit 
           # with 
  3. Then, you need to make it executable with the following command:
    $> chmod +x exist.sh
  4. Now, it's time to try out our script. We try it with directories that do exist and with those that don't. We also check the exit code after each run:
    $> ./exist.sh  
    You must supply exactly one argument. 
    Example: ./exist.sh /etc 
    $> echo $?
    1
    $> ./exist.sh /etc 
    File or directory exists 
    $> echo $?
    0
    $> ./exist.sh /asdfasdf 
    File or directory does not exist
    $> echo $?
    3
  5. Now that we know that it's working and leaving the correct exit codes, we can write one-liners to use our script together with, for example, echo to print a text stating whether the file or directory exists:
    $> ./exist.sh / && echo "Nice, that one exists"
    File or directory exists
    Nice, that one exists
    $> ./exist.sh /asdf && echo "Nice, that one exists"
    File or directory does not exist
  6. We can also write a more complicated one-liner—one that takes advantage of the unique error code 3 we assigned to "file not found" in our script. Note that you shouldn't type > at the start of the second line. This character is automatically inserted by the shell when you end the first line with a backslash to indicate the continuation of a long line:
    $> ./exist.sh /asdf &> /dev/null; \
    > if [ $? -eq 3 ]; then echo "That doesn't exist"; fi
    That doesn't exist

How it works…

The test program is a small utility designed to test files and directories, compare 
values, and so on. In our case, we used it to test if the specified file or directory exists (-e for exist).

The test program doesn't print anything; it just exits in silence. It does, however, leave a return value. It is that return value that we check with the $? variable. It's also the very same variable we check in the script's if statements.

There are some other special variables in the script that we used. The first one was $#, which contains the number of arguments passed to the script. It works like argc in C. At the very start of the script, we compared if $# is not equal to 1 (-ne stands for not equal). If $# is not equal to 1, an error message is printed and the script aborts with code 1.

The reason for putting $# inside quotes is just a safety mechanism. If, in some unforeseen event, $# were to contain spaces, we still want the content to be evaluated as a single value, not two. The same thing goes for the quotes around the other variables in the script.

The next special variable is $0. This variable contains argument 0, which is the name of the program, just as with argv[0] in C, as we saw in Chapter 1, Getting the Necessary Tools and Writing Our First Linux Programs.

The first argument to the program is stored in $1, as shown in the test case. The first argument in our case is the supplied filename or directory that we want to test.

Like our C programs, we want our scripts to exit with a relevant return value (or exit code, as it is also called). We use exit to leave the script and set a return value. In case the user doesn't supply precisely one argument, we exit with code 1, a general error code. And if the script is executed as it should, and the file or directory exists, we exit with code 0. If the script is executed as it should, but the file or directory doesn't exist, we exit with code 3, which isn't reserved for a particular use, but still indicates an error (all non-zero codes are error codes). This way, other scripts can fetch the return value of our script and act upon it.

In Step 5, we did just that—act upon the exit code from our script with the following command:

$> ./exist.sh / && echo "Nice, that one exists"

&& means "and". We can read the whole line as an if statement. If exist.sh is true—that is, exit code 0—then execute the echo command. If the exit code is anything other than 0, then the echo command is never executed.

In Step 6, we redirected all the output from the script to /dev/null and then used a complete if statement to check for error code 3. If error code 3 is encountered, we print a message with echo.

There's more…

There are a lot more tests and comparisons we can do with the test program. They are all listed in the manual; that is, man 1 test.

If you are unfamiliar with Bash and shell scripting, there is a lot of useful information in the manual page, man 1 bash.

The opposite of && is || and is pronounced "or." So, the opposite of what we did in this recipe would be as follows:

$> ./exist.sh / || echo "That doesn't exist"
File or directory exists
$> ./exist.sh /asdf || echo "That doesn't exist"
File or directory does not exist
That doesn't exist

See also

If you want to dig deep into the world of Bash and shell scripting, there is an excellent guide at The Linux Documentation Project: https://tldp.org/LDP/Bash-Beginners-Guide/html/index.html.