Good Programming Practices - System Calls

Discussion in 'C' started by poornaMoksha, Dec 31, 2011.

  1. poornaMoksha

    poornaMoksha New Member

    Joined:
    Jan 29, 2011
    Messages:
    150
    Likes Received:
    33
    Trophy Points:
    0
    Occupation:
    Software developer
    Location:
    India
    This article is the Part-II (Part-I here) of the series where in we are focusing on good coding practices. In the Part-I we discussed the importance of the assert macro/function for detecting and debugging bugs in program. In this part we will focus on good coding practices while dealing with system calls.

    Note: As always, we will be discussing everything in the Linux environment.

    Working with system calls



    There are certain points that one should keep in mind while dealing with system calls.
    • One should have in and out knowledge of the system call like what it does, what it returns in case of success/failure, what could be the failure reasons etc.
    • For the knowledge above, one should go through the man page of each system call. Man pages provide enough knowledge that is required for a particular system call.
    • One important thing is the return values of the system calls. Whenever a system call fails it gives a specific return value. In different failure situations, different return values are returned. Understanding of these return values is very important since they are required to convey to the user the real failure of the call.

    Reasons for system call failures



    There can be following reasons for the failure of system calls :
    1. The system runs out of resources or your program exceeds the limit of resources it can use. For example, too much memory allocation, too much open files etc.
    2. IF a system call tries to do certain task and that task requires some privileges to be done and suppose your program does not have sufficient privileges then also a system call fails.
    3. Invalid arguments. This is another major reason for the failure. If you pass an UN-allocated pointer or an invalid file descriptor etc then also a system call would fail.
    4. There can also be some external reasons for which a system call fails. Like if the system call tries to access a hardware and that hardware is faulty or currently not available then also a system call would fail.
    5. Some external event like a signal might also interrupt a system call.

    Error codes from system calls



    Following are some important points on the error codes returned by system calls :
    1. Most of the system calls return zero on success and non-zero on failure. There could be some exceptions like malloc() which returns non-zero address on success while NULL on failure.
    2. These numeric return values could well signify that a system call has failed but do not provide any meaningful information about the exact reason.
    3. The variable errno does this for us. This is a global variable defined in errno.h and this variable changes its values whenever a system call fails or succeeds. It helps us in a way that when this variable is passed to the function strerror() then this function returns a meaningful string containing the reason of failure.
    Please read the below information on errno and strerror() function as it contains some valuable points.

    Some more information on errno :

    The <errno.h> header file defines the integer variable errno, which is set by system calls and some library functions in the event of an error to indicate what went wrong. Its value is significant only when the return value of the call indicated an error (i.e., -1 from most system calls; -1 or NULL from most library functions); a function that succeeds is allowed to change errno.

    Valid error numbers are all non-zero; errno is never set to zero by any system call or library function.

    For some system calls and library functions (e.g., getpriority(2)), -1 is a valid return on success. In such cases, a successful return can be distinguished from an error return by setting errno to zero before the call, and then, if the call returns a status that indicates that an error may have occurred, checking to see if errno has a non-zero value.

    errno is defined by the ISO C standard to be a modifiable lvalue of type int, and must not be explicitly declared; errno may be a macro. errno is thread-local; setting it in one thread does not affect its value in any other thread.

    Some more information on strerror() :

    The strerror() function returns a pointer to a string that describes the error code passed in the argument errnum, possibly using the LC_MESSAGES part of the current locale to select the appropriate language. This string must not be modified by the application, but may be modified by a subsequent call to perror(3) or strerror(). No library function will modify this string.

    The strerror() function return the appropriate error description string, or an "Unknown error nnn" message if the error number is unknown.

    Following is an example describing the usage of errno and strerror() function :

    Code:
    #include<stdio.h> 
    #include<errno.h> 
    #include<string.h> 
     
    int main(void) 
    { 
        FILE *fd = NULL; 
     
        // Reset errno as its not guaranteed to be zero. 
        errno = 0; 
     
        // Open a non existent file    
        fd = fopen("ssss","r"); 
        if(NULL == fd) 
        { 
            // Use strerror to display the error string 
            printf("\nThe error is : [%s]\n",(char*)strerror(errno)); 
            return -1; 
        } 
     
     
        return 0; 
    }
    In the code above, we try to open a non existent file and display the error string.

    Lets see the output :

    Code:
    $ ./errno  
     
    The error is : [No such file or directory]
    So we see that from a numeric value 'errno', we are able to generate a meaningful error string "No such file or directory" through the function strerror().

    There is one more function that does this kind of work, the perror() function.

    The signature and the details of the API is given below
    Code:
    #include <stdio.h> 
     
    void perror(const char *s); 
     
    #include <errno.h> 
     
    const char *sys_errlist[]; 
    int sys_nerr; 
    int errno;
    From the man page :

    Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

    sys_errlist, sys_nerr: _BSD_SOURCE

    The routine perror() produces a message on the standard error output, describing the last error encountered during a call to a system or library function. First (if s is not NULL and *s is not a null byte ('\0')) the argument string s is printed, followed by a colon and a blank. Then the message and a new-line.

    To be of most use, the argument string should include the name of the function that incurred the error. The error number is taken from the external variable errno, which is set when errors occur but not cleared when non-erroneous calls are made.

    The global error list sys_errlist[] indexed by errno can be used to obtain the error message without the newline. The largest message number provided in the table is sys_nerr -1. Be careful when directly accessing this list because new error values may not have been added to sys_errlist[].

    When a system call fails, it usually returns -1 and sets the variable errno to a value describing what went wrong. (These values can be found in <errno.h>.) Many library functions do likewise. The function perror() serves to translate this error code into human-readable form. Note that errno is undefined after a successful library call: this call may well change this variable, even though it succeeds, for example because it internally used some other library function that failed. Thus, if a failing call is not immediately followed by a call to perror(), the value of errno should be saved.

    Handling errors in a program with allocated resources



    There are times when a program is lengthy enough to contain various functions and there could be memory allocations in many of the functions. Now suppose in one function you encounter an error and you decide to return from the middle of the function then you must take care of the fact that all the allocated resources need to be deallocated first otherwise it would lead to a memory leak each time this kind of failure is returned from the function.

    For example take this situation in the function:
    1. Allocate a buffer
    2. Open file
    3. Read from the file
    4. Close the file
    5. Release the buffer
    And now suppose the call in step 2 returns failure, then you should release the buffer in error handling and then return from the function.

    Conclusion



    To conclude, In this article, we learned some good concepts of dealing with system call failures and resource allocation and deallocation in failure scenarios. Hope this articles helps those who are new to C programming on Linux.

    Stay tuned for more!!!!!
     
    shabbir and Trinity like this.
  2. sura

    sura Banned

    Joined:
    Aug 4, 2011
    Messages:
    47
    Likes Received:
    1
    Trophy Points:
    0
    Location:
    India,Tamil Nadu.
    nice article.
     
  3. Scripting

    Scripting John Hoder

    Joined:
    Jun 29, 2010
    Messages:
    421
    Likes Received:
    57
    Trophy Points:
    0
    Occupation:
    School for life
    Location:
    /root

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice