1

I've MAMP running httpd server as _www and I'd like to assign the temporary directory as part of the shell script (bash).

I've tried to use $TMPDIR as per these posts:

however it points to /var/folders/vp/tlt7xf791gl1_v56m0xdmrph0000gn/T/ and this folder has not necessary permissions, so Apache server can't create any files in there.

Based on this:

$ /usr/bin/stat -F $TMPDIR /tmp /private/tmp 
drwx------ 104 kenorb staff 3536 Oct 14 22:21:05 2015 /var/folders/vp/tlt7xf791gl1_v56m0xdmrph0000gn/T//
lrwxr-xr-x 1 root wheel 11 Apr  9 13:08:03 2014 /tmp@ -> private/tmp
drwxrwxrwt 112 root wheel 3808 Oct 14 22:12:04 2015 /private/tmp/

My www user which I care about:

$ id _www
uid=70(_www) gid=70(_www) groups=70(_www),12(everyone),61(localaccounts),401(com.apple.sharepoint.group.1),100(_lpoperator)

I can't use /tmp, because it's linked to my /private/tmp (which name states it's private).

Therefore how do I determine my correct temporary directory on OS X, so I and my httpd server can use it?

I don't want to hardcode value, as the script would be useless when used on different environments (e.g. on non-MAMP environment), so the location can vary.

2
  • 1
    Are you worried /private is just to "hide" the usual directories from / and not to mean you can't store files there. It's better form to use $TMPDIR instead of /tmp, but writing to /tmp is generally fine on OS X if you prefer using that fixed path...
    – bmike
    Oct 14, 2015 at 21:28
  • The point of $TMPDIR being in /var/... is that it has the correct permissions - so what users is the directory open for and what user does the script run as? - private just is a historic so that the OS can be on a read only file system and /private is a mount to a local file system that can be written to
    – mmmmmm
    Oct 14, 2015 at 21:43

3 Answers 3

3

I would write to /tmp (which is equivalent to writing to /private/tmp ) since it's writable by all users and not just administrative users. It's there for compatibility and for bonus points, your script can clean up files or cut them if they grow too large.

I don't have an authoritative reference to why /private was called that, but it's always been open to writing and isn't restricted like $TMPDIR is from non-admin users.

The real question is what user are you running apache - a member of the ADMIN group or just a normal non-admin user?

Apple documents this in the Secure Coding Guide with the admonition that writing files to publicly readable locations is inherently insecure and that it's better to place files in more protected locations that aren't readable by all users. See the Race Conditions and Secure File Operations section and this link on creating secure temporary files. By using the mkstemp POSIX call you ensure a unique file is created and can control access with umask.

Oddly (to me), mktemp when run by a non-admin user can still make a temp file in $TMPDIR so it's clearly using escalated privileges to hand off that more "secure" file to non-admin users. Pretty cool.

5
  • Is there any variable or function which can use to determine that folder? Or I've to do conditionally?
    – kenorb
    Oct 14, 2015 at 21:36
  • @kenorb - which folder? - $TMPDIR is the variable that gives the temporary folder (also TMPDIR is for all users each user has a different one /tmp is shared so you get conflicts between users)
    – mmmmmm
    Oct 14, 2015 at 21:59
  • @kenorb write to the path /tmp and bob's your uncle. You could make it a variable, but there's no point as it doesn't change.
    – bmike
    Oct 14, 2015 at 22:23
  • 1
    I think it'll be easier to use /tmp, initially I was confused by the symbolic link which had 0755 on it, but the destination is actually 1777, so I hope it'll work in all cases. Thanks.
    – kenorb
    Oct 14, 2015 at 22:27
  • You can't change /tmp or /private/tmp but all users can write there. Insecure, but it gets the job done with little muss or fuss. You're on it with the 1777 permissions.
    – bmike
    Oct 14, 2015 at 22:35
1

As for workaround, I've found so far that php command can determine the right temporary directory from the www perspective (assuming PHP CLI has the same settings as server):

$ php -r "echo ini_get('upload_tmp_dir');"
/Applications/MAMP/tmp/php

It's possible also to use mktemp to check where the temporary files are created for specific user (as $TMPDIR would fail), e.g.:

$ sudo -uwww bash -c 'dirname $(mktemp)'
/tmp

So in summary this could be extended to something like:

#!/usr/bin/env bash
WWW_USER="$(ps axo user,group,comm | egrep "(apache|httpd)" | grep -v ^root | uniq | cut -d\  -f 1)"
PHP="$(which php)"

if [ -n "$PHP" ]; then
  TMP="$(php -r "echo ini_get('upload_tmp_dir');")"
elif [ -n "$WWW_USER" ]; then
  TMP="$(sudo -uwww bash -c 'dirname $(mktemp)')"
elif [ -n "$TMPDIR" ]; then
  TMP="$TMPDIR"
else
  TMP="/tmp"
fi
0

Short answer: getconf DARWIN_USER_TEMP_DIR (command line interface), or for another user, e.g. www: sudo -u www getconf DARWIN_USER_TEMP_DIR.


Too long; don't read unless you want a story on how I got to the short answer:

On macOS 10.13.6 High Sierra, I can see the rough equivalences of $TMPDIR for different users, e.g. current user and www, in a few ways that I know of, using bash --version 5.1.8:

  • Each of the below blocks yields two different folders under /var/folders ending in /T/.

  • command rm is compat for an alias rm=… in my ~/.bash_profile.

  1. Using mktemp - this is the same as kenorb's answer but oddly with a different result - also doing a cleanup:

    (sudo --validate && function inner(){ t=$(mktemp) && dirname "$t" ; command rm "$t" ;} && for u in $USER www ;do sudo -u $u bash -c "$(declare -fp inner|awk 'FNR>3{print _}{_=$0}')" ;done)
    
  2. Reading Apple's opensource for my macOS version's package "shell_cmds"'s mktemp, I wrote a short program in C:

    (sudo --validate && function outer(){ for u in $USER www;do sudo -u $u $1;done;} ; cleanup(){ command rm $1 $1.c;} ; b=/tmp/$$ && cat >$b.c && clang -o$b $b.c && chmod +x $b && outer $b ; cleanup $b) << END
    #include <limits.h>
    #include <stdio.h>
    #include <unistd.h>
     int main() {
      char tmpbuf[PATH_MAX];
      if (confstr(_CS_DARWIN_USER_TEMP_DIR, tmpbuf, sizeof(tmpbuf)) < 1) {
       return 1;
      } else {
       printf("%s\\n", tmpbuf);
       return 0;
      }
     }
    END
    
  3. Writing this, after having tried getconf _CS_DARWIN_USER_TEMP_DIR and getconf CS_DARWIN_USER_TEMP_DIR, still not checking man 1 getconf as I should've, I at least finally checked my earlier usage of confstr - actually getconf DARWIN_USER_DIR - realising the transformation needed for .c confstr(int name,…) to .sh getconf var, i.e. getconf $(sed s/^_CS_// <<< $name), e.g. getconf $(sed s/^_CS_// <<< _CS_DARWIN_USER_TEMP_DIR) equal to getconf DARWIN_USER_TEMP_DIR, thus:

    (sudo --validate && for u in $USER www ;do sudo -u $u getconf DARWIN_USER_TEMP_DIR ;done)
    

Too long; note to self: read the f-ing manual - man 1 mktemp –› man 3 confstr –› man 1 getconf


Addendum re nohillside's comment:

When user runs stat -F $TMPDIR, again in bash, under a shell - login, interactive, and? - it shows something like drwx------ 410 user staff …. To be clear, this is user's incarnation of TMPDIR.

I don't know who's setting up the TMPDIR variable, and it is unset both in sudo -u www bash -lic 'declare -p TMPDIR' and an apache httpd -V 2.4.57 (macports - not MAMP) running the php --version 7.4.33 script below.

So, instead of using TMPDIR, and still not using /tmp, we can get the value that TMPDIR would have.

When I run stat -F $(sudo -u www getconf DARWIN_USER_TEMP_DIR) it shows drwx------ 3 _www _www …. Here _www is aka www.

This way, httpd can write to its user www's incarnation of what would be TMPDIR, but instead retrieved by getconf DARWIN_USER_TEMP_DIR; user can also read/write under the same location, depending on user's configured group membership of www (or ACL?), or using sudo; all the while not having www use any other locations (like /tmp).

<pre>
    posix_getuid                : <?=var_dump($uid      =   posix_getuid    (       )                                       )?>
    posix_getpwuid … name       : <?=var_dump(              posix_getpwuid  ($uid   )['name']                               )?>
    getenv keys                 : <?php foreach(getenv() as $k=>$v){ ?><?="\n"?>
        <?=$k?>
    <?php } ?><?="\n"?>
    shell_exec TMPDIR           : <?=var_dump(  trim(     shell_exec("           echo \$TMPDIR "                    )   )   )?>
    exec       TMPDIR           : <?=var_dump(                  exec("           echo \$TMPDIR "                        )   )?>
    shell_exec TMPDIR           : <?=var_dump(  trim(     shell_exec("bash -lic 'echo \$TMPDIR'"                    )   )   )?>
    exec       TMPDIR           : <?=var_dump(                  exec("bash -lic 'echo \$TMPDIR'"                        )   )?>
    
  Using getconf DARWIN_USER_TEMP_DIR:
    shell_exec stat -F getconf …: <?=var_dump(  trim(     shell_exec("stat -F   $(getconf DARWIN_USER_TEMP_DIR)"    )   )   )?>
    
  I happen to have some file there, so it's clear that "drwx------@ 3 _www" works as usual:
    shell_exec ls -l   getconf …: <?=                     shell_exec("ls -l     $(getconf DARWIN_USER_TEMP_DIR)"            )?>
</pre>

Outputs:

    posix_getuid                : int(70)
    posix_getpwuid … name       : string(4) "_www"
    getenv keys                 : 
        PATH    
        XPC_SERVICE_NAME    
        XPC_FLAGS   
        XML_CATALOG_FILES   
    shell_exec TMPDIR           : string(0) ""
    exec       TMPDIR           : string(0) ""
    shell_exec TMPDIR           : string(0) ""
    exec       TMPDIR           : string(0) ""
    
  Using getconf DARWIN_USER_TEMP_DIR:
    shell_exec stat -F getconf …: string(98) "drwx------ 3 _www _www 102 Aug  9 12:56:01 2023 /var/folders/…/T//"
    
  I happen to have some file there, so it's clear that "drwx------@ 3 _www" works as usual:
    shell_exec ls -l   getconf …: total 0
-rw-------  1 _www  _www  0 Aug  9 12:35 tmp.L1WG1XuJ

Bonus: Some (re)search for this answer yielded an answer on unix.stackexchange with some other details on macOS mktemp, and saying the alpha numerics in TMPDIR comes from a base32 encoding of the equivalent of dscl . -read /Users/$name GeneratedUID and a "concatenation" w/ the "(BSD) user ID", so there is another way to calculate TMPDIR, but my light jab at that encoding failed, so that's left for the reader.

3
  • Doesn’t getconf just return the temp dir the OP mentioned in the question (which isn‘t accessible to Apache/httpd)
    – nohillside
    Aug 9 at 12:48
  • edited @nohillside
    – vike
    Aug 9 at 18:53
  • 1
    Ah, so much detail, great. Got it now, the idea is to run getconf as _www. Agreed, that will work.
    – nohillside
    Aug 9 at 19:59

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .