Version 19 of Creating Temporary Files

Updated 2008-09-17 18:44:52 by dnagle

Purpose: Show how to create a temp file.

DL


Tcl doesn't have a built-in command to create a temporary file and there are no built-in variables for temp directories, so you have to do a little work.

Best is to figure out the temp directory separately. Then you can create multiple files in it.

 switch $tcl_platform(platform) {
    unix {
        set tmpdir /tmp   # or even $::env(TMPDIR), at times.
    } macintosh {
        set tmpdir $::env(TRASH_FOLDER)  ;# a better place?
    } default {
        set tmpdir [pwd]
        catch {set tmpdir $::env(TMP)}
        catch {set tmpdir $::env(TEMP)}
    }
 }

Ingemar Hansson - Added double colons above to "env" since it isn't global declared

If you just want one temp file:

 set filename [file join $tmpdir [pid]]

If you want multiple tmpfiles, consider appending things like the application name, counter, time stamp, etc. For example:

 set file [file join $tmpdir $appname.[pid].[incr ::globalCounter]]

DKF - Or create in a subdirectory/subfolder

 if {![file exists [file join $tmpdir [pid]]]} {
    file mkdir [file join $tmpdir [pid]]
 }
 set file [file join $tmpdir [pid] [incr ::globalCounter]]

Michael Schlenker - Be aware of potential race conditions while opening temp files. The linux secure programming howto has some nice examples for the linux platform: http://en.tldp.org/HOWTO/Secure-Programs-HOWTO/avoid-race.html

To be sure to get a new tempfile (and not some evil symlink) try something like:

 set access [list RDWR CREAT EXCL TRUNC]
 set perm 0600
 if {[catch {open $file $access $perm} fid ]} {
     # something went wrong 
     error "Could not open tempfile."
 } 
 # ok everything went well

I had some luck with the following procedure on a UNIX box. Maybe, somebody can check if it works for windows.

 proc tempfile {prefix suffix} {
     set chars "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
     set nrand_chars 10
     set maxtries 10
     set access [list RDWR CREAT EXCL TRUNC]
     set permission 0600
     set channel ""
     set checked_dir_writable 0
     set mypid [pid]
     for {set i 0} {$i < $maxtries} {incr i} {
  set newname $prefix
  for {set j 0} {$j < $nrand_chars} {incr j} {
      append newname [string index $chars \
       [expr ([clock clicks] ^ $mypid) % 62]]
  }
  append newname $suffix
  if {[file exists $newname]} {
      after 1
  } else {
      if {[catch {open $newname $access $permission} channel]} {
   if {!$checked_dir_writable} {
       set dirname [file dirname $newname]
       if {![file writable $dirname]} {
    error "Directory $dirname is not writable"
       }
       set checked_dir_writable 1
   }
      } else {
   # Success
   return [list $newname $channel]
      }
  }
     }
     if {[string compare $channel ""]} {
  error "Failed to open a temporary file: $chanel"
     } else {
  error "Failed to find an unused temporary file name"
     }
 }

Igor Volobouev


Stu - My version of mkstemp(3)

 proc randazAZchar {} {
     return [format %c [expr {int(rand() * 26) + [expr {int(rand() * 10) > 5 ? 97 : 65}]}]]
 }
 proc randazAZstring {length} {
     set s {}
     for {set i $length} {$i > 0} {incr i -1} {append s [randazAZchar]}
     return $s
 }
 proc makeTempFile {template} {
     if {[set xi [string first "X" $template]] == -1} {
         set n $template
         set p {}
         set rl 0
     } else {
         set n [string range $template 0 [expr {$xi - 1}]]
         set p [pid]
         set rl [expr {[string length $template] - $xi - [string length $p]}]
         if {$rl < 0} {
             set p [string range $p 0 end-[expr {$rl * -1}]]
             set rl 0
         }
     }
     for {set i [expr {int(pow(26, ($rl <= 6 ? $rl : 6)))}]} {$i > 0} {incr i -1} {
         set fn $n[randazAZstring $rl]$p
         if {![catch {open $fn {CREAT EXCL RDWR} 0600} fd]} {
             file delete $fn
             return $fd
         }
         if {[lindex $::errorCode 1] ne "EEXIST" || [lindex $::errorCode 0] ne "POSIX"} {
             break
         }
     }
     return -code error -errorcode $::errorCode $fd
 }

wld

Have a look at GUID and UUID for generating unique filenames for temp-files.

---

Tcllib now provides commands for tempdir (based on python's) and tempfile (based on the above)

http://tcllib.sourceforge.net/doc/fileutil.html


Category Security | Category Tutorial | Category File