Making Files Temporary

Once a temporary file is created, a program must release it when finished with it. Otherwise, the temporary file directory will fill with many abandoned files over time. Calling unlink(2) is trivial, but making sure it is done when the program prematurely exits is more of a challenge.

Using unlink(2) to Make Files Temporary

One way to make sure that the temporary file is released is to release it immediately after it is created and opened. This looks illogical to those who are new to UNIX, but a UNIX file can exist after it has been unlinked, as long as the file remains open. When the last open file descriptor for the file is closed, the disk space is reclaimed by the UNIX kernel.

Recall function tmpfile(3), which creates temporary files with no pathname. It uses this general procedure:

  1. Generate a unique temporary filename.

  2. Create and open the file.

  3. Call unlink(2) on the temporary filename. This effectively makes the file nameless, but the file itself exists as long as it remains open.

  4. Call fdopen(3) to open a FILE stream, using the open file descriptor from step 2.

  5. Return the FILE stream pointer to the caller.

This temporary but nameless file has two advantages:

  • The file has already been released. No temporary file cleanup is required.

  • No other process can subsequently open and tamper with the temporary file. This also provides a measure of privacy.

The second point is still subject to a window of opportunity, since the file must be created and then passed to unlink(2). However, the main advantage presented here is that no matter how your program exits or aborts, the temporary file will not be left in a directory, since it has already been unlinked.

Performing Exit Cleanup

There are situations in which the unlink(2) approach is not convenient. If the file must be closed and then reopened, then you have no choice but to keep a name associated with the temporary file. For this reason, the C programmer must rely on other methods, such as the atexit(3) function.

Using the atexit(3) Function

The C library function atexit(3) allows the programmer to register a function that can be used for all types of cleanup tasks. Of primary interest here is the removal of temporary files. The function synopsis for atexit(3) is as follows:

#include <stdlib.h>

int atexit(void (*func)(void));

The argument provided to atexit(3) is simply the function pointer to a function, declared as follows:

void func(void) {
    /* My cleanup code… */
}

The function atexit(3) returns 0 when it registers the function successfully and returns non-zero when it fails. FreeBSD returns -1 and an error code in errno when atexit(3) fails, but be sure to read the Warning in this section about this. For maximum portability, it is best to test for zero to see if atexit(3) succeeded.

The functions registered by atexit(3) are called in the reverse order from which they are registered.

Note

FreeBSD and UnixWare 7 document that a minimum of 32 functions may be registered. Additional entries are limited only by available memory. Linux appears to support as many registrations as remaining memory permits.

HPUX 11 and IBM's AIX 4.3 state that the atexit(3) function is limited to a maximum of ATEXIT_MAX registered functions. For HPUX, this is defined by the include file <limits.h>; for AIX, it is <sys/limits.h>.

The limit for Solaris 8 is defined by sysconf(3C) using the parameter _SC_ATEXIT_MAX.


Warning

FreeBSD documents that atexit(3) sets errno when -1 is returned (ENOMEM is one documented error returned). Linux (Red Hat 6.0) documentation states that atexit(3) returns -1 if it fails, and errno is not set.

SGI's IRIX 6.5, UnixWare 7, and HPUX 11 document that they return "non-zero when [they] fail." No error codes for errno are documented.

For these reasons, always test for a successful return (a 0 return) of atexit(3) for maximum portability. Additionally, the errno code should be ignored unless the specific platform is taken into account.


The program in Listing 8.6 shows an example that calls on the atexit(3) function. This causes a cleanup function to be called upon program termination.

Code Listing 8.6. atexit.c—A Program Using atexit(3) to Register a Cleanup Function
1:   /* atexit.c */
2:
3:   #include <stdio.h>
4:   #include <stdlib.h>
5:   #include <unistd.h>
6:   #include <string.h>
7:   #include <errno.h>
8:
9:   extern char *tempnam(const char *tmpdir,const char *prefix);
10:
11:  static char *tf_path = NULL;    /* Temp. File Pathname */
12:
13:  /*
14:   * Cleanup function :
15:   */
16:  static void
17:  mr_clean(void) {
18:
19:      puts("mr_clean() started:");
20:
21:      /*
22:       * Here we assume, that if tf_path is not NULL, that
23:       * the main program has not released the temporary
24:       * file on its own.
25:       */
26:      if ( tf_path != NULL ) {
27:          printf("unlinking temp. file %s
",tf_path);
28:
29:          /*
30:           * Unlink the temporary file, and release the
31:           * pathname string :
32:           */
33:          if ( unlink(tf_path) == -1 )
34:              fprintf(stderr,"%s: unlink(2)
",strerror(errno));
35:          free(tf_path);          /* Free the pathname string */
36:          tf_path = NULL;         /* Indicate that this is released */
37:      }
38:
39:      puts("mr_clean() ended.");
40:  }
41:
42:  /*
43:   * Main program :
44:   */
45:  int
46:  main(int argc,char *argv[]) {
47:      FILE *tmpf = 0;             /* Temp. File stream */
48:
49:      atexit(mr_clean);           /* Register our cleanup func */
50:  
51:      /*
52:       * Create a temp. file pathname :
53:       */
54:      if ( !(tf_path = tempnam("/tmp","tmp-")) ) {
55:          fprintf(stderr,"%s: creating temp file.
",strerror(errno));
56:          abort();
57:      }
58:      printf("Temp. file is %s
",tf_path);
59:
60:      /*
61:       * Create, open and write to the temp. file :
62:       */
63:      if ( !(tmpf = fopen(tf_path,"w+")) ) {
64:          fprintf(stderr,"%s: opening %s
",strerror(errno),tf_path);
65:          abort();
66:      }
67:      fprintf(tmpf,"PID %ld was here.
",(long)getpid());
68:
69:      /*
70:       * Normal program exit, without unlinking the temp file:
71:       */
72:      fclose(tmpf);               /* Notice no unlink(2) here.. */
73:      return 0;                   /* Normal program exit */
74:  }

An examination of the program shows that first the mr_clean() function is registered with atexit(3), in line 49. Lines 54–72 create a temporary file, write to it, and then close it. The program takes a normal exit in line 73.

Exiting causes the registered function mr_clean() to be called to release the temporary file that was created. This is demonstrated by the compile and run session shown, as follows:

$ make atexit
cc -c -D_POSIX_C_SOURCE=199309L -D_POSIX_SOURCE -Wall atexit.c
cc atexit.o -o atexit
$ ./atexit
Temp. file is /tmp/tmp-D52582
mr_clean() started:
unlinking temp. file /tmp/tmp-D52582
mr_clean() ended.
$

The program announces (line 58 of Listing 8.6) that it has created the temporary file /tmp/tmp-D52582 and then silently returns from the main() program (line 73). This causes the registered cleanup function mr_clean() to be called, which then produces the last three lines of output, indicating that it has called unlink(2) to remove the temporary file.

One of the major portability concerns that you should bear in mind is that some platforms will limit the number of registered functions to a maximum of 32. This is especially critical if you are designing a C library, where you have no direct control over how the user is using atexit(3). If the caller of your library has already used up all 32 possible registrations, then your library will be out of luck.

One way that this problem can be circumvented is by registering one special function, which can then invoke as many additional cleanup functions as you choose.

Using C++ Destructors

The C++ programmer has the capability to rely on destructors for cleanup operations. Listing 8.7 shows a very simple example of a class named Temp that makes use of a temporary file.

Code Listing 8.7. destruct.cc—A C++ Program Using a Destructor for Temporary File Cleanup
1:   // destruct.cc
2:
3:   #include <stdio.h>
4:   #include <stdlib.h>
5:   #include <unistd.h>
6:   #include <string.h>
7:   #include <stdarg.h>
8:   #include <errno.h>
9:
10:  extern "C" {
11:      extern char *tempnam(const char *tmpdir,const char *prefix);
12:  }
13:
14: ////////////////////////////////////////////////////////////
15:  // A demonstration class, showing how a temp file can
16:  // be used within a C++ class, with automatic
17:  // destruction.
18:  ////////////////////////////////////////////////////////////
19:
20:  class Temp {
21:      char    *tf_path;           // Temp. File Pathname
22:      FILE    *tf;                // Open temp. file
23:  public:
24:      Temp();                     // Constructor
25:      ~Temp();                    // Destructor
26:      Temp &printf(const char *format,...);
27:      Temp &rewind();             // Rewind
28:      Temp &gets(char *buf,int bufsiz);
29:  } ;
30:
31:
32:  ////////////////////////////////////////////////////////////
33:  // Constructor :
34:  ////////////////////////////////////////////////////////////
35:
36:  Temp::Temp() {
37:
38:      /*
39:       * Create a temp. file pathname :
40:       */
41:      if ( !(tf_path = tempnam("/tmp","tmp-")) )
42:          throw errno;            // Temp. file generation failed
43:  
44:      /*
45:       * Create, open and write to the temp. file :
46:       */
47:      if ( !(tf = fopen(tf_path,"w+")) )
48:          throw errno;            // Open failed
49:
50:      printf("Created temp file: %s
",tf_path);
51:  }
52:
53:  ////////////////////////////////////////////////////////////
54:  // Destructor :
55:  ////////////////////////////////////////////////////////////
56:
57:  Temp::~Temp() {
58:      fclose(tf);                 // Close the open file
59:      unlink(tf_path);            // Delete the temp file
60:      delete tf_path;             // Free pathname string
61: 
62:      write(1,"Temp::~Temp() called.
",22);
63:  }
64:
65:  ////////////////////////////////////////////////////////////
66:  // The printf() method :
67:  //
68:  // Allows the caller to write to the temp. file with the
69:  // convenience of printf().
70:  ////////////////////////////////////////////////////////////
71:
72:  Temp &
73:  Temp::printf(const char *format,...) {
74:      va_list ap;
75:
76:      va_start(ap,format);
77:      vfprintf(tf,format,ap);
78:      va_end(ap);
79:
80:      return *this;
81:  }
82:
83:
84:  ////////////////////////////////////////////////////////////
85:  // Rewind the temp. file :
86:  ////////////////////////////////////////////////////////////
87:
88:  Temp &
89:  Temp::rewind() {
90:      ::rewind(tf);               // Rewind the temp file
91:      return *this;
92:  }
93:  
94:  ////////////////////////////////////////////////////////////
95:  // Read back one text line from the temp. file :
96:  ////////////////////////////////////////////////////////////
97:
98:  Temp &
99:  Temp::gets(char *buf,int bufsiz) {
100:     int e;
101:
102:     if ( !fgets(buf,bufsiz,tf) ) {
103:         if ( feof(tf) )         // EOF ?
104:             throw EOF;          // Indicate EOF
105:         e = errno;
106:         clearerr(tf);
107:         throw e;                // Throw the error
108:     }
109:
110:     return *this;
111: }
112:
113: ////////////////////////////////////////////////////////////
114: // Main program :
115: ////////////////////////////////////////////////////////////
116:
117: int
118: main(int argc,char *argv[]) {
119:     Temp tf;                    // Create a temp file
120:     char buf[256];
121:
122:     (void) argc;
123:     (void) argv;
124:
125:     // Announce start of program :
126:     printf("PID %ld started:
",(long)getpid());
127:
128:     // Now write one text line to the temp file :
129:     tf.printf("PID %ld was here.
",(long)getpid());
130:
131:     tf.rewind();                // Rewind temp file
132:
133:     // Now read back the one text line from the temp file
134:
135:     try {
136:         tf.gets(buf,sizeof buf);
137:     }  catch ( int e ) {
138:         fprintf(stderr,"%s: tf.gets()
",strerror(e));
139:         exit(1);
140:     }
141:
142:     printf("Read back: %s
",buf);
143:
144:     puts("Now exiting..");
145:     return 0;
146: }
147:
148: // End destruct.cc
						

The program shown in Listing 8.7 declares a class Temp in lines 20–29. The class method Temp::printf() allows the caller to format a text line to be written to the temporary file. Method Temp::rewind() rewinds the temporary file, and method Temp::gets() allows the caller to retrieve one text line from the temporary file.

The constructor is implemented in lines 36–51. Note the call to the C function tempnam(3) in line 41, and the call to fopen(3) in line 47 to create and open the file. The pathname is stored in private member tf_path, and the open FILE is saved in private member tf (declared in lines 21 and 22).

When the Temp object is destroyed, the destructor, which is implemented in lines 57–63, is called upon. The destructor closes the temporary file, deletes the pathname of the file, and then frees the pathname string (lines 58–60).

The main() program constructs one instance of the Temp class in line 119 (the object is named tf). The object is destroyed when the main() program exits in line 145 (the return statement).

Lines 129–142 simply exercise some of the methods of the object tf. One text line is written to the temporary file, the file is rewound, and the one text line is read back.

Compiling and running this program should yield results similar to the following:

$ make destruct
cc -c -D_POSIX_C_SOURCE=199309L -D_POSIX_SOURCE -Wall -fhandle-exceptions destruct.cc
cc destruct.o -o destruct -lstdc++
$ ./destruct
PID 52982 started:
Read back: Created temp file: /tmp/tmp-Q52982

Now exiting..
Temp::~Temp() called.
$

The line starting with Read back: shows how the temporary file was being exercised. The line Temp::~Temp() called. shows the output from the write(2) call in line 62 of the destructor, proving that the destructor was called. In fact, if the pathname is checked, it will be nonexistent:

$ ls -l /tmp/tmp-Q52982
ls: /tmp/tmp-Q52982: No such file or directory
$

This proves that the destructor did its job.

While this technique seems to address the cleanup issue, you should be aware that pitfalls still exist. For example, if you change the statement in line 145 that now reads return 0; to read exit(0);, you will discover that the destructor for the object tf is not called. If your application has calls to exit(3) sprinkled throughout, you may still wish to use the services of the atexit(3) function.

Avoiding Cleanup with _exit(2)

Sometimes it is necessary for a program to exit without invoking any cleanup at all. This is highly desirable when something has gone wrong and you want your program to leave things as they are. This allows you to keep all temporary files around so that they can be inspected for troubleshooting purposes. This can be done with the _exit(2) function:

#include <unistd.h>

void _exit(int status);

The function is called in the same manner as exit(3), except that no atexit(3) processing is invoked when _exit(2) is called.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.156.35