mkstemp

Stephen Weeks MLton@sourcelight.com
Thu, 21 Jun 2001 16:20:57 -0700


> Strange  about  the 0600 vs. 0666.  The manual says earlier versions of glibc
> use 0666 but newer ones use 0600.  I would argue that you pretty  much  never
> want  to  use  anything  besides 0666 and 0777, with any extra security being
> handled by the umask.  Still, it all depends on how paranoid you want to  be.
> If you look at the info entry for mkstemp, it says:
>         The  file  is opened using mode `0600'.  If the file is meant
>         to  be  used  by  other  users  this  mode  must  be  changed
>         explicitly.
> Any  way,  it isn't a big deal since either way if you care about it you just
> call open directly.  As to the mode, I would just leave mkstemp as in  the  C
> case.   After  all,  it  is  what  people  expect, and it is just a trade off
> between flexibility and convenience.

OK.  I will leave the mode hardwired as 600.

> By the way, looking at the info  pages  I  see  that  there  is  a  mkdtemp()
> function,  but  no  man  page.   That uses mode 0700 (again, minus umask).  I
> don't think I would bother with mkdtemp(), just because it isn't useful  that
> often.  For that you can just use use Posix.FileSys.mkdir.

OK.

> As  to  the  source to mkstemp, you don't want to do the Random.seed on every
> call to tempName, do you?  If so, then it really MUST  be  /dev/urandom,  not
> /dev/random.

OK.  I switched to a pseudo-random generator.

> Also  100  tries  isn't  enough.  Also, you have to detect the
> reason for the failure.  Looping is the right thing to do (I  would  probably
> go  forever)  if  it  fails  by EEXIST

OK.  I will continuing looping iff it fails with EEXIST.

> but for other errors you want to fail
> right away, no retries.  (The usual case is failure because of  permissions.)

OK.

> To  me, the only question is if you should have mkstemp return both an in and
> out stream.

Or neither. 

> I  missed  something.   What  is the argument for having MLton.TextIO.mkstemp
> return only the string?  This would be very confusing since the file has been
> made.  

The only argument in favor of an outstream over an instream is that surely you
are going to write the file you just created.  However, there are cases in MLton
(and surely elsewhere) where that writing is done by another process, and hence
the outstream is useless too.  So I can see an argument for dropping the
outstream and just returning the name (after all, it is call mkstemp, not
mkandopenstmp).  Anyways, I am happy either way.

> Also  I  don't  understand the suffix.

It is so the temp file can have a certain suffix, which is useful, e.g. if you
are about to call gcc on it.

>   I think that the way to go is
> either
>     string -> string * TextIO.outstream
> or
>     string -> string * TextIO.instream * TextIO.outstream
> where the string argument is just like in the  C  mkstemp,  but  without  the
> trailing "XXXXXX".

I still vote for
      {prefix: string, suffix: string} -> string * TextIO.outstream
or possibly two versions (mkstemp and mkstemps).

Here is the latest.

      local
	 val chars =
	    "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
	 val n = Word.fromInt (String.size chars)
	 val r: word ref = ref 0w0
      in
	 fun tempName {prefix, suffix} =
	    let
	       val _ = r := Random.seed ()
	       val unique =
		  String.tabulate
		  (6, fn _ =>
		   let
		      val w = !r
		      val c = String.sub (chars,
					  Word.toInt (Word.mod (w, n)))
		      val _ = r := Word.div (w, n)
		   in c
		   end)
	    in
	       concat [prefix, unique, suffix]
	    end
      end

      fun mkstemp arg: string * outstream =
	 let
	    fun loop () =
	       let
		  val name = tempName arg
		  open Posix.FileSys
	       in
		  (name,
		   newOut (createf (name, O_WRONLY, O.flags [O.excl],
				    let open S
				    in flags [irusr, iwusr]
				    end)))
	       end handle e as PosixError.SysErr (_, SOME s) =>
		  if s = Posix.Error.exist
		     then loop ()
		  else raise e
	 in
	    loop ()
	 end