mktemp on macOS Not Honoring $TMPDIR – Solutions

environment-variablesmacosmktemp

I've noticed this before, but it was brought up again as I was answering "How to move directory into a directory with the same name?":

The mktemp utility on macOS does not behave the same as the utility of the same name on Linux or BSD (or least OpenBSD) with respect to the TMPDIR environment variable.

To create a temporary file in the current directory, I can usually say

tmdfile=$(TMPDIR=. mktemp)

or

tmpfile=$(TMPDIR=$PWD mktemp)

(and similarly for a temporary directory with mktemp -d).

On macOS, I will have to force the utility to use the current directory by giving it an actual template, as in

tmpfile=(mktemp ./tmp.XXXXXXXX)

because using the more convenient tmpfile=$(TMPDIR=. mktemp) would ignore the TMPDIR variable and create the file under /var/folders/qg/s5jp5ffx2p1fxv0hy2l_p3hm0000gn/T or in a similarly named directory.

The manual for mktemp on macOS mentions that

If the -t prefix option is given, mktemp will generate a template string based on the prefix and
the _CS_DARWIN_USER_TEMP_DIR configuration variable if available. Fallback locations if
_CS_DARWIN_USER_TEMP_DIR is not available are TMPDIR and /tmp.

On my system, _CS_DARWIN_USER_TEMP_DIR appears to be unset:

$ getconf _CS_DARWIN_USER_TEMP_DIR
getconf: no such configuration parameter `_CS_DARWIN_USER_TEMP_DIR'

but e.g.

tmpfile=$(TMPDIR=. mktemp -t hello)

still creates a file under /var/folders/.../ (also when using $PWD in place of .).

I'm noticing that

$ getconf DARWIN_USER_TEMP_DIR
/var/folders/qg/s5jp5ffx2p1fxv0hy2l_p3hm0000gn/T/

but this doesn't help me much as I wouldn't know how to change this value.

The macOS mktemp utility is said to come from FreeBSD, which in turn got it from OpenBSD (which must have been quite a while ago).

Question:

Is this a bug (or omission) in the macOS implementation of mktemp? How do I change the DARWIN_USER_TEMP_DIR value (or _CS_DARWIN_USER_TEMP_DIR mentioned by the manual) from within a script (I would ideally want to unset it so that $TMPDIR takes precedence)?

Best Answer

/var/folders/qg/s5jp5ffx2p1fxv0hy2l_p3hm0000gn/

This is your Darwin user local directory. Its name is simply a modified base 32 encoding of the concatenation your MacOS User UUID and your MacOS (BSD) user ID. The first two letters of the encoding are used as a "bucket" system to attempt to keep directory sizes low. Those two characters are the encoded first 10 bits of the User UUID, because in base 32 one digit is of course 5 bits.

Its subdirectories are your user local temp and user local cache directories. Their names used to be -Caches- and -Tmp- but those have been shortened to C and T. It should be apparent that all of these names are fixed and unchangeable, unless you are willing to change your user ID or user UUID.

When an application calls confstr(_CS_DARWIN_USER_TEMP_DIR,…), the C library first tries to ensure that you have a user local directory, then tries to ensure that you have a user local temp directory within it.

Ensuring that you have a user local directory is non-trivial, because you do not have write access to /var/folders. So there is a dirhelper Mach launch dæmon that runs with superuser privileges and that securely creates these directories, responding to Mach IPC calls from applications from within the implementation of confstr() in their C libraries. You do have write access to the user local directory (once created) and so the C libraries just mkdir() its children directly if they do not already exist.

If this succeeeds, the mktemp program never looks at the value of the TMPDIR environment variable, because the fallback in mktemp's code is from calling confstr() to calling getenv() not the other way around. confstr(_CS_DARWIN_USER_TEMP_DIR,…) will almost always succeed. Its failure modes are things like the dirhelper launch dæmon not being able to be run, or the attempt to create the T subdirectory failing with an error other than that the directory already exists.

You could put something other than a directory as T, but this will be regularly cleaned up by the dirhelper launch dæmon, which is also what deletes stuff in /var/folders. Disabling the dirhelper launch dæmon will cause problems of its own, not the least of which will be /var/folders not getting cleaned. Denying yourself write permission on your user local directory will potentially interfere with all other uses of it, it being used for more than just a T subdirectory.

Your best option (aside from supplying a template) is to make T a symbolic link, but this is still far from good because it will of course affect all running applications of yours that might, at the very same instant, be wanting to create a temporary file.

Neither DARWIN_USER_TEMP_DIR nor _CS_DARWIN_USER_TEMP_DIR are variable names. They are names, for the getconf utility and for the confstr() library function, of a configuration string.

Related Question