Thread: mkstemp() and sigaction()

  1. #1
    Registered User
    Join Date
    Mar 2024
    Posts
    20

    mkstemp() and sigaction()

    So I'm writing a program that stores data in a temporary file that is deleted when the program exits. And I would like the program to delete the file when it exits because of a signal as well. This is the code I came up with:

    Code:
    #define _POSIX_C_SOURCE 200809L
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <signal.h>
    
    
    
    static void     exit_with_msg(const char *restrict msg);
    static void     handle(int signo);
    
    
    
    char *tempf = (char *)0;
    
    
    
    int main(void) {
        struct sigaction sa;
        sa.sa_handler = handle;
        if (sigfillset(&sa.sa_mask) == -1) {
            exit_with_msg("Couldn't initialize signal mask.\n");
        }
        sa.sa_flags = SA_RESETHAND;
        
        // Assign handler to signals....
        
        
        char template[] = "/tmp/progXXXXXX";
        int fd;
        if ((fd = mkstemp(template)) == -1) {
            exit_with_msg("Couldn't create temporary file\n");
        }
        tempf = template;
        
        // Write to file....
        
        (void) unlink(tempf);
        (void) close(fd);
        
        return 0;
    }
    
    
    
    static void exit_with_msg(const char *restrict msg) {
        fputs(msg, stderr);
        exit(1);
    }
    
    
    
    static void handle(int signo) {
        (void) unlink(tempf);
        (void) raise(signo);
    }
    Because the signal handler doesn't accept parameters, I used a global variable to unlink the file inside the signal handler.

    However, there is a problem. The standard says....
    the behavior is undefined if the signal handler refers to any object other than errno with static storage duration other than by assigning a value to an object declared as volatile sig_atomic_t
    But sig_atomic_t is an integer type! And only assigning a value from the signal handler is "permitted", not reading it!

    The reason why I'm creating the file with mkstemp() is because I want to honor the TMPDIR environment variable. But for the sake of simplicity in the code I assume its value is /tmp.

    So how can I approach this problem?

  2. #2
    Registered User
    Join Date
    Dec 2017
    Posts
    1,664
    Can you structure your writing loop to refer to a volatile sig_atomic_t?
    Something like:
    Code:
    // global
    volatile sig_atomic_t continue_loop = 1;
    
    static void handle(int signo) {
        continue_loop = 0;
    }
    
    // in main
    while (continue_loop) {
        // write to file...
    }
    All truths are half-truths. - A.N. Whitehead

  3. #3
    Registered User
    Join Date
    Mar 2024
    Posts
    20
    Quote Originally Posted by john.c View Post
    Can you structure your writing loop to refer to a volatile sig_atomic_t?
    Something like:
    Code:
    // global
    volatile sig_atomic_t continue_loop = 1;
    
    static void handle(int signo) {
        continue_loop = 0;
    }
    
    // in main
    while (continue_loop) {
        // write to file...
    }
    But this approach has some undesirable implications:
    • Once the signal handler exits, the loop continues at the point where it was interrupted. Ideally, once the signal is received the program should just do the cleanup and exit.
    • Because of the previous point, as the signal handler is no longer running, if another signal is received the signal handler will be called again. Whereas I would like the signal handler to be called just once and once it is being run or once it has finished all signals should be blocked (that is, except for SIGKILL and SIGSTOP because those can't be blocked or ignored).

  4. #4
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,675
    You can remove the file as soon as you've created it, but it will persist as a real file system inode until you close the file.

    Code:
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdarg.h>
    #include <unistd.h>
    #include <sys/types.h>
    
    static void exit_with_msg(const char *restrict msg);
    static void pause_with_message(const char *restrict msg, ...);
     
    int main(void) {
        pid_t me = getpid();
        printf("My PID=%d\n", (int)me);
        char template[] = "/tmp/progXXXXXX";
        int fd;
        if ((fd = mkstemp(template)) == -1) {
            exit_with_msg("Couldn't create temporary file\n");
        }
        pause_with_message("ls -l /tmp/prog* ; lsof -p %d",me);
        (void) unlink(template);
        // Now you will see something like
        // /tmp/progCcuE5C (deleted)
        // and file not found in the directory
        pause_with_message("ls -l /tmp/prog* ; lsof -p %d",me);
    
        char m1[] = "hello world\n";
        write(fd,m1,strlen(m1));
        off_t p1 = lseek(fd, 0, SEEK_CUR);
        printf("Written %d so far\n", (int)p1);
    
        // go back a bit
        off_t p2 = lseek(fd, -6, SEEK_CUR);
        printf("Step back Pos=%d\n", (int)p2);
    
        char world[6] = { 0 };
        read(fd,world,5);
        off_t p3 = lseek(fd, 0, SEEK_CUR);
        printf("Read <<%s>>, final pos=%d\n", world, (int)p3);
    
        pause_with_message("lsof -p %d",me);
        (void) close(fd);
        // As soon as the fd is closed, it's gone for good.
        pause_with_message("lsof -p %d",me);
    
        return 0;
    }
    
    static void exit_with_msg(const char *restrict msg) {
        fputs(msg, stderr);
        exit(1);
    }
    
    static void pause_with_message(const char *restrict msg, ...) {
        char cmd[100];
        va_list ap;
        va_start(ap,msg);
        vsnprintf(cmd, sizeof(cmd), msg, ap);
        printf(">>> %s\n",cmd);
        system(cmd);
        printf("Press enter to continue > ");
        getchar();
        va_end(ap);
    }
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  5. #5
    Registered User
    Join Date
    Mar 2024
    Posts
    20
    Quote Originally Posted by Salem View Post
    You can remove the file as soon as you've created it, but it will persist as a real file system inode until you close the file.

    Code:
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdarg.h>
    #include <unistd.h>
    #include <sys/types.h>
    
    static void exit_with_msg(const char *restrict msg);
    static void pause_with_message(const char *restrict msg, ...);
     
    int main(void) {
        pid_t me = getpid();
        printf("My PID=%d\n", (int)me);
        char template[] = "/tmp/progXXXXXX";
        int fd;
        if ((fd = mkstemp(template)) == -1) {
            exit_with_msg("Couldn't create temporary file\n");
        }
        pause_with_message("ls -l /tmp/prog* ; lsof -p %d",me);
        (void) unlink(template);
        // Now you will see something like
        // /tmp/progCcuE5C (deleted)
        // and file not found in the directory
        pause_with_message("ls -l /tmp/prog* ; lsof -p %d",me);
    
        char m1[] = "hello world\n";
        write(fd,m1,strlen(m1));
        off_t p1 = lseek(fd, 0, SEEK_CUR);
        printf("Written %d so far\n", (int)p1);
    
        // go back a bit
        off_t p2 = lseek(fd, -6, SEEK_CUR);
        printf("Step back Pos=%d\n", (int)p2);
    
        char world[6] = { 0 };
        read(fd,world,5);
        off_t p3 = lseek(fd, 0, SEEK_CUR);
        printf("Read <<%s>>, final pos=%d\n", world, (int)p3);
    
        pause_with_message("lsof -p %d",me);
        (void) close(fd);
        // As soon as the fd is closed, it's gone for good.
        pause_with_message("lsof -p %d",me);
    
        return 0;
    }
    
    static void exit_with_msg(const char *restrict msg) {
        fputs(msg, stderr);
        exit(1);
    }
    
    static void pause_with_message(const char *restrict msg, ...) {
        char cmd[100];
        va_list ap;
        va_start(ap,msg);
        vsnprintf(cmd, sizeof(cmd), msg, ap);
        printf(">>> %s\n",cmd);
        system(cmd);
        printf("Press enter to continue > ");
        getchar();
        va_end(ap);
    }
    There's a problem though. You see, I oversimplified the signal handler just to keep it simple to picture the problem much better. But maybe I oversimplified it too much....

    This is the REAL signal handler my program uses:
    Code:
    #define _POSIX_C_SOURCE 200809L
    
    #include <unistd.h>
    #include <signal.h>
    #include <termios.h>
    
    struct termios saveterm;
    char *tempf;
    
    int main(void) {
        /* Here, mkstemp() is called to change the value of tempf,
           but tcgetattr() is also called to save the terminal
           modes, because canonical input and echo are disabled
           because at some point the program is paused until the
           user presses any key. */
    }
    
    static void handle(int signo) {
        (void) tcsetattr(STDIN_FILENO, TCSANOW, &saveterm);
        
        if (signo != SIGQUIT) {
            (void) unlink(tempf);
        }
        
        (void) raise(signo);
    }
    So the program when receiving a signal must:
    • Unlink the temporary file, except if it was a SIGQUIT. This is purely conventional, even the standard says:
      Receipt of the SIGQUIT signal should generally cause termination (unless in some debugging mode) that would bypass any attempted recovery actions.
      But honestly, I'm happy if this isn't the case. The problem however, is the following point:
    • Restore the terminal modes as they were before changing them. Because canonical input and echo are disabled, I could just do this in the signal handler:
      Code:
      static void handle(int signo) {
          struct termios restoreterm;
          if (tcgetattr(STDIN_FILENO, &restoreterm) != -1) {
              restoreterm.c_lflag |= (ICANON | ECHO);
              (void) tcsetattr(STDIN_FILENO, TCSANOW, &restoreterm);
          }
      }
      But if I disabled them before running the program, if a signal is received it would enable them. So the program is strictly speaking not "restoring" the terminal modes.


    And this is why I asked how to access variables inside the signal handler.... But if there's another way to do it, I'll happily do it that way.

    Hope this is not too much of a hassle!!!
    Last edited by Sabidos; 07-10-2024 at 08:49 AM.

  6. #6
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,675
    You deliberately want the temp file to remain if the signal is SIGQUIT?

    > And this is why I asked how to access variables inside the signal handler
    Well you use globals, as you do now.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  7. #7
    Registered User
    Join Date
    Mar 2024
    Posts
    20
    Quote Originally Posted by Salem View Post
    You deliberately want the temp file to remain if the signal is SIGQUIT?
    That's right. As far as I know this is a pretty common thing to do. I'm not making this up I swear. I use GCC as my main compiler and even the documentation says so:
    Certain kinds of cleanups are best omitted in handling SIGQUIT. For example, if the program creates temporary files, it should handle the other termination requests by deleting the temporary files. But it is better for SIGQUIT not to delete them, so that the user can examine them in conjunction with the core dump.
    And it makes sense to me. I personally have written many scripts that create temporary files and don't delete them when receiving a SIGQUIT. I once detected an unexpected behavior (it depended on the temporary file's contents) and I pressed Ctrl-\ to check the file contents. It can be helpful in such cases. And because the signal is usually delivered by the user, you can think of it as a "hey I noticed something wrong, stop right now and create a core dump and don't clean any temporary files" signal

    Quote Originally Posted by Salem View Post
    > And this is why I asked how to access variables inside the signal handler
    Well you use globals, as you do now.
    But that's undefined behavior, isn't it?

  8. #8
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,675
    How is it UB?

    Sure, there is potential for a data race if tcgetattr(STDIN_FILENO, &saveterm); in main itself causes a signal to occur, but if that does happen, your chances of success are limited no matter what you do.

    Your signal handler just becomes tcsetattr(STDIN_FILENO, TCSANOW, &saveterm); and you don't have to worry about whether the calling environment may (or may not) have set a particular mode.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  9. #9
    Registered User
    Join Date
    Mar 2024
    Posts
    20
    Quote Originally Posted by Salem View Post
    How is it UB?

    Sure, there is potential for a data race if tcgetattr(STDIN_FILENO, &saveterm); in main itself causes a signal to occur, but if that does happen, your chances of success are limited no matter what you do.

    Your signal handler just becomes tcsetattr(STDIN_FILENO, TCSANOW, &saveterm); and you don't have to worry about whether the calling environment may (or may not) have set a particular mode.
    Maybe I'm wrong, but this part of the standard says a signal handler may only change the value of a volatile sig_atomic_t variable. It also says that if any other type's value is changed or if a value of any type is read it is UB.

    the behavior is undefined if the signal handler refers to any object other than errno with static storage duration other than by assigning a value to an object declared as volatile sig_atomic_t
    Here's a link to the page where this is mentioned: General Information

  10. #10
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,675
    A longer quote:
    the behavior is undefined if the signal handler refers to any object other than errno with static storage duration other than by assigning a value to an object declared as volatile sig_atomic_t, or if the signal handler calls any function defined in this standard other than one of the functions listed in the following table.

    The following table defines a set of functions that shall be async-signal-safe. Therefore, applications can call them, without restriction, from signal-catching functions. Note that, although there is no restriction on the calls themselves, for certain functions there are restrictions on subsequent behavior after the function is called from a signal-catching function (see longjmp).
    It's hard to imagine how some of those functions on the async-signal-safe could possibly be used in a meaningful way without reading some previously established state (like your saveterm for example). I suppose you could declare it volatile struct termios saveterm; just to emphasis to the reader that it is shared between normal and signal contexts.

    A structure like a termios written to by one async-signal-safe function like tcgetattr in normal context should be safe to use by another async-signal-safe function like tcsetattr in signal context.

    The use of volatile sig_atomic_t should be for scalars in your code that might fall foul of some kinds of optimisations.
    Eg
    Code:
    int i;  // yes, really should be volatile sig_atomic_t
    
    int main ( ) {
      for ( i = 0 ; i < 1000 ; i++ ) {
        if ( i == 500 ) raise();
      }
    }
    
    void handle(int signo) {
      if ( i == 500 ) {
        // woo hoo!
      }
    }
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  11. #11
    Registered User
    Join Date
    Mar 2024
    Posts
    20
    Quote Originally Posted by Salem View Post
    It's hard to imagine how some of those functions on the async-signal-safe could possibly be used in a meaningful way without reading some previously established state (like your saveterm for example)
    I agree, the standard isn't very helpful in this respect. Even the examples they provide aren't very helpful.Except for this one:
    Code:
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <semaphore.h>
    #include <time.h>
    #include <assert.h>
    #include <errno.h>
    #include <signal.h>
    
    
    sem_t sem;
    
    
    static void
    handler(int sig)
    {
        int sav_errno = errno;
        static const char info_msg[] = "sem_post() from handler\n";
        write(STDOUT_FILENO, info_msg, sizeof info_msg - 1);
        if (sem_post(&sem) == -1) {
            static const char err_msg[] = "sem_post() failed\n";
            write(STDERR_FILENO, err_msg, sizeof err_msg - 1);
            _exit(EXIT_FAILURE);
        }
        errno = sav_errno;
    }
    
    
    int
    main(int argc, char *argv[])
    {
        struct sigaction sa;
        struct timespec ts;
        int s;
    
    
        if (argc != 3) {
            fprintf(stderr, "Usage: %s <alarm-secs> <wait-secs>\n",
                argv[0]);
            exit(EXIT_FAILURE);
        }
    
    
        if (sem_init(&sem, 0, 0) == -1) {
            perror("sem_init");
            exit(EXIT_FAILURE);
        }
    
    
        /* Establish SIGALRM handler; set alarm timer using argv[1] */
    
    
        sa.sa_handler = handler;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        if (sigaction(SIGALRM, &sa, NULL) == -1) {
            perror("sigaction");
            exit(EXIT_FAILURE);
        }
    
    
        alarm(atoi(argv[1]));
    
    
        /* Calculate relative interval as current time plus
           number of seconds given argv[2] */
    
    
        if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
            perror("clock_gettime");
            exit(EXIT_FAILURE);
        }
        ts.tv_sec += atoi(argv[2]);
    
    
        printf("main() about to call sem_timedwait()\n");
        while ((s = sem_timedwait(&sem, &ts)) == -1 && errno == EINTR)
            continue;       /* Restart if interrupted by handler */
    
    
        /* Check what happened */
    
    
        if (s == -1) {
            if (errno == ETIMEDOUT)
                printf("sem_timedwait() timed out\n");
            else
                perror("sem_timedwait");
        } else
            printf("sem_timedwait() succeeded\n");
    
    
        exit((s == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
    }
    (Source: sem_timedwait)

    I haven't yet used semaphores, but I suppose this example relies on them in order to synchronize the many possible times the signal handler may be called???? I just don't know

    You know, I'm starting to consider what @john.c said. But instead of a loop, I can enclose every single instruction in a check wether the volatile sig_atomic_t variable has a certain value. Like this:
    Code:
    #define _POSIX_C_SOURCE 200809L
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <signal.h>
    #include <termios.h>
    
    
    
    static void     exit_with_msg(const char *restrict msg);
    static void     handle(int signo);
    
    
    
    volatile sig_atomic_t interrupted = 0;
    
    
    
    int main(void) {
        char template[] = "/tmp/prognameXXXXXX";
        int tempfd = -1;
        
        struct termios saveterm;
        // Obtain terminal modes with tcgetattr()...
        
        struct sigaction sa;
        sa.sa_handler = handle;
        if (sigfillset(&sa.sa_mask) == -1) {
            exit_with_msg("Couldn't initialize signal mask.\n");
        }
        sa.sa_flags = SA_RESETHAND;
        
        // Assign singal hadler to signals...
        
        if (interrupted == 0) { 
            tempfd = mkstemp(template);
        }
        
        if (interrupted == 0) {
            // Open file... Exit if it fails
        }
        
        if (interrupted == 0) {
            // Write to file... Exit if it fails
        }
        
        /* Close file here. No need to check if it was interrupted since it will be
        closed in any case */
        
        (void) tcsetattr(STDIN_FILENO, TCSANOW, &saveterm);
        if (interrupted != 0) {
            if (interrupted != SIGQUIT && tempfd != -1) {
                (void) unlink(template);
            }
            
            (void) raise(interrupted);
        }
        else {
            (void) unlink(template);
        }
        
        return 0;
    }
    
    
    
    static void exit_with_msg(const char *restrict msg) {
        fputs(msg, stderr);
        exit(1);
    }
    
    
    
    static void handle(int signo) {
        // Block all signals
        sigset_t mask;
        if (sigfillset(&mask) != -1) {
            (void) sigprocmask(SIG_SETMASK, &mask, (sigset_t *)0);
        }
        
        // Set flag
        interrupted = signo;
    }
    Appart from the continuous check, it also differs from @john.c's proposal in that I block all signals once the handler is called, and the default value of the variable is zero. And there's an advantage about this: I can assign the received signal's value to that variable and I can use it to check not only whether a signal was received but also which one was received. sig_atomic_t may be signed or unsigned (the standard isn't very helpful here either), but luckily the value 0 is reserved for the null signal. Yay!

    The only problem I see about this approach is that SIG_ATOMIC_MAX (maximum value of sig_atomic_t) may be less than SIGRTMAX (maximum value of the range of signals)

    So now I have to decide between using global variables which cause undefined behavior, getting to learn semaphores before working with signal handlers which may be helpful in this scenario, or forget about semaphores for now and continue to write my program doing the functional but ugly repetitive checks....

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. mkstemp inside a loop
    By jsh in forum C Programming
    Replies: 2
    Last Post: 12-27-2011, 01:45 PM
  2. struct sigaction
    By ssharish2005 in forum C Programming
    Replies: 1
    Last Post: 07-17-2011, 08:41 AM
  3. Replies: 5
    Last Post: 12-17-2007, 03:24 AM
  4. Example of mkstemp
    By Dan17 in forum C Programming
    Replies: 4
    Last Post: 09-13-2006, 07:20 PM
  5. mkstemp question!!!
    By zynnel in forum C Programming
    Replies: 2
    Last Post: 03-26-2003, 09:28 PM

Tags for this Thread