2

Okay so after spending ages developing a script ready for testing, I've now learned the target environment doesn't have mkstemp (I foolishly believed every unix-y OS had that), nor does it seem to have any of the other common temp-file utilities. If there are any other widely available temp-file commands do let me know, but I don't think I have any to use.

So, this means I need to implement a form of mkstemp myself. Now the easiest way is something like tmp="/tmp/tmp.$$.$RANDOM" and while that works it doesn't guarantee there won't be a collision of file names, so I need to test for that, however the problem is that between testing for the file, and creating one, the file could end up unexpectedly being created, so it may not be a suitable method either, at least on it's own.

In the past I had to implement an equivalent to lockfile myself, which I was able to do by using a temporary file moved into place using mv as a cheat (if it returns an error then the lock already existed). I'm inclined to think I can maybe do something similar using some operation that will fail if the file already exists, but I'm not sure what the best way would be.

I know that use of /tmp/tmp.$$.$RANDOM is unlikely to result in collisions, but I'd like to implement this correctly if I can, as the script needs to create quite a lot of temporary files that are then moved into place, and I may need to do the same in other scripts later on, so it'd be nice to do it correctly!

EDIT: I just realised I've been referring to mktemp everywhere instead of mkstemp which is the one I really want to replicate (where a file is created safely for you). I think I've corrected mistaken mentions, please forgive the confusion!

Haravikk
  • 3,109
  • 1
  • 33
  • 46
  • 1
    If you are working with a local filesystem, you can achieve safe tempfile creation in a portable shell script by leveraging `mkdir`. See here: http://stackoverflow.com/a/731634/722332 – Mattie Aug 03 '13 at 15:52

2 Answers2

2

Usually, the command line tool for creating temporary directories is called mktemp, not mkstemp. I'm not sure if it's safe to use on all platforms. If it isn't, you can attempt to make a directory with a strict umask and fail when the directory already exists.

mkdtemp() {
    old_mask=$(umask)
    umask 077

    name="/tmp/tmp.$$.$RANDOM"
    mkdir "$name" && echo "$name"
    retval=$?

    umask $old_mask

    return $retval
}

Usage:

tempdir=$(mkdtemp) || report_failure

Since this tries to create the directory (an atomic operation) instead of checking whether it exists and fails when the name it generates is taken, this operation is safe. It is prone to a denial-of-service attack, though, where an attacker creates many temporary directories just to let the above function fail.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • Unless I'm mistaken `mktemp` just returns a valid random name, while `mkstemp` actually creates the file for you as well, avoiding the possibility of getting a name from `mktemp` that someone else then takes before you actually create the file by writing to it. Your code is useful for creating a temporary directory, but can it be done for a file? Creating a unique directory and writing into a file within it is certainly an option though! – Haravikk Aug 03 '13 at 17:27
  • @Haravikk: yes, but `mkstemp` doesn't generate-and-create atomatically, so effectively it's the same as my recipe. The security benefit over `mktemp` is that the caller is no longer responsible for safe file creation. – Fred Foo Aug 04 '13 at 08:12
  • I'm marking this as the answer, as it's (in essence) what I'm now using; i.e - I grab a temporary folder then create files with fixed names inside for convenience until I'm done, then I discard the whole folder. It's probably a better way to structure my code than grabbing several files via `mkstemp` anyway. – Haravikk Aug 07 '13 at 17:07
0

Using @larsmans's answer I think I may have arrived at one of my own. Instead of using mkdir I'm going to use cp on a known file, specifically /dev/null, which effectively creates an empty file, but should do so atomically!

For those interested here's the simplified version of the code (please forgive any typos):

mkstemp() {
    umask_old=$(umask)
    umask 077

    name="/tmp/tmp.$$.$RANDOM"
    cp -n /dev/null "$name" 2> /dev/null && echo "$name"
    retval=$?

    umask "$umask_old"
    return "$retval"
}

I've simplified the above to match larsmans's, as my actual solution will loop until a file is created or a limit on attempts is reached, and I'm using behaviour that more closely matches how mkstemp actually picks names (using a template with trailing X's), but I think the above shows what I'm doing, and it seems to work exactly as I require!

So thanks larsmans, your solution is a fine stand-in for mkdtemp and the above should do the same as a stand-in for mkstemp. I will probably mark this as the answer, but I'll leave it for a little while in case anyone notices any problems with the code.

EDIT: Unfortunately this method requires a version of cp with the not widely supported -n flag (do not overwrite), but even this won't always work if the target file exists but is empty, as cp will consider it to already be a valid copy and exit with a status of 0. For this reason mkdir may still be the better option.

Haravikk
  • 3,109
  • 1
  • 33
  • 46
  • Just `touch` will also create an empty file. And setting `umask=077` doesn't actually change the file creation mask. – Fred Foo Aug 04 '13 at 08:12
  • Thanks for spotting that typo! I tried using `touch` but it doesn't always produce errors if the file already exists, especially if it has less restrictive permissions, but also if you are running as root, in which case `touch` never seems to fail at all if the file already exists, so I think `cp` may be the better option. – Haravikk Aug 04 '13 at 09:38
  • oh, that's right, `touch` just updates the timestamp. Never mind! – Fred Foo Aug 04 '13 at 10:11
  • Hmm, I think I've found a problem with `cp`; the `-n` option (do not overwrite, which I forgot to include above) isn't widely supported enough for my needs (systems that have it have `mkstemp` too!) which means it may just overwrite an existing file if it has loose permissions, rather than failing like I want. While I could possibly use the `-i` flag instead (prompt before overwrite) it won't work if the existing file is empty, i.e - it matches `/dev/null` so can already be considered to be a copy, causing `cp` to exit with a `0` status. Bah! Looks like `mkdir` is still the best option then! – Haravikk Aug 04 '13 at 17:32