Skip to content

Instantly share code, notes, and snippets.

@laggardkernel
Last active April 14, 2024 06:30
Show Gist options
  • Save laggardkernel/38566f4473068c065f1a1ef15e6e1b4a to your computer and use it in GitHub Desktop.
Save laggardkernel/38566f4473068c065f1a1ef15e6e1b4a to your computer and use it in GitHub Desktop.
Change umask based on working directory #zsh #direnv

Default umaks on macOS is 0077, which is different with most Linux distributions.

There're pros and cons about this very decision made by Apple. For the good part, temporary files generate under /tmp, $TMPDIR are accessible by the user himself only. These locations could be alternatives to $XDG_RUNTIME_DIR.

While, the problem is that since the new files are accessible by yourself only, it's inconvenient to share files with other users.

It's easy to change umask globally with a LauchAgent, or change it for shells only in the shell initialization files. But the solution I provide is more flexible, the umask is set per-directory with the help of direnv.

The solution is based on zimbatm's answer from direnv issue #509.

I improved zimbatm's code for macOS, where the default umask value is 0077.

Dynamic umask with direnv and custom hook

# example .envrc file
export UMASK=0022
function _umask_hook {
  if [[ -n $UMASK ]]; then
    umask "$UMASK"
  elif [[ $OSTYPE == darwin* ]]; then
    umask 0077
  else
    umask 0022
  fi
}

# To make the code more reliable on detecting the default umask
function _umask_hook {
  # Record the default umask value on the 1st run
  [[ -z $DEFAULT_UMASK ]] && export DEFAULT_UMASK="$(builtin umask)"

  if [[ -n $UMASK ]]; then
    umask "$UMASK"
  else
    umask "$DEFAULT_UMASK"
  fi
}

# zsh hooks
# trigger _umask_hook once working dir is changed
# precmd is not enough, cause it may not be triggered when cwd is changed by ZLE widget
add-zsh-hook chpwd _umask_hook
# make sure _umask_hook is run on startup
add-zsh-hook precmd _umask_hook

# bash
# Append `;` if PROMPT_COMMAND is not empty
PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND;}_umask_hook"

Hook direnv onto chpwd

For the time being, direnv hook initialization for zsh doesn't support chpwd hook. If the pull request GH-514 has not been merged when you see this page. Please comment out eval "$(direnv hook zsh)" and hook direnv on chpwd manually with following code,

if (( $+commands[direnv] )) && ! (( $+functions[_direnv_hook] )); then
  _direnv_hook() {
    eval "$(command "direnv" export zsh)";
  }
  typeset -agU precmd_functions;
  if [[ -z ${precmd_functions[(r)_direnv_hook]} ]]; then
    precmd_functions=( _direnv_hook ${precmd_functions[@]} )
  fi

  typeset -agU chpwd_functions;
  if [[ -z ${chpwd_functions[(r)_direnv_hook]} ]]; then
    chpwd_functions=( _direnv_hook ${chpwd_functions[@]} )
  fi
fi

Trigger chpwd on shell startup

In fact, adding _umaks_hook, _direnv_hook into precmd is redundant, cause hooking them onto chpwd is enough. The only problem is that, chpwd is not run on shell startup. A solution is here: Trigger chpwd Hook on Startup.

chpwd

Executed whenever the current working directory is changed.

precmd

Executed before each prompt. Note that precommand functions are not re-executed simply because the command line is redrawn, as happens, for example, when a notification about an exiting job is displayed.

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment