Test if a directory is writable by a given UID?
Because I had to make some changes to @chepner's answer in order to get it to work, I'm posting my ad-hoc script here for easy copy & paste. It's a minor refactoring only, and I have upvoted chepner's answer. I'll delete mine if the accepted answer is updated with these fixes. I have already left comments on that answer pointing out the things I had trouble with.
I wanted to do away with the Bashisms so that's why I'm not using arrays at all. The ((
arithmetic evaluation))
is still a Bash-only feature, so I'm stuck on Bash after all.
for f; do
set -- $(stat -Lc "0%a %G %U" "$f")
(("$1" & 0002)) && continue
if (("$1" & 0020)); then
case " "$(groups "$USER")" " in *" "$2" "*) continue ;; esac
elif (("$1" & 0200)); then
[ "$3" = "$USER" ] && continue
fi
echo "$0: Wrong permissions" "$@" "$f" >&2
done
Without the comments, this is even fairly compact.
Here's a long, roundabout way of checking.
USER=johndoe
DIR=/path/to/somewhere
# Use -L to get information about the target of a symlink,
# not the link itself, as pointed out in the comments
INFO=( $(stat -L -c "%a %G %U" "$DIR") )
PERM=${INFO[0]}
GROUP=${INFO[1]}
OWNER=${INFO[2]}
ACCESS=no
if (( ($PERM & 0002) != 0 )); then
# Everyone has write access
ACCESS=yes
elif (( ($PERM & 0020) != 0 )); then
# Some group has write access.
# Is user in that group?
gs=( $(groups $USER) )
for g in "${gs[@]}"; do
if [[ $GROUP == $g ]]; then
ACCESS=yes
break
fi
done
elif (( ($PERM & 0200) != 0 )); then
# The owner has write access.
# Does the user own the file?
[[ $USER == $OWNER ]] && ACCESS=yes
fi
You can use sudo
to execute the test in your script. For instance:
sudo -u mysql -H sh -c "if [ -w $directory ] ; then echo 'Eureka' ; fi"
To do this, the user executing the script will need sudo
privileges of course.
If you explicitly need the uid instead of the username, you can also use:
sudo -u \#42 -H sh -c "if [ -w $directory ] ; then echo 'Eureka' ; fi"
In this case, 42
is the uid of the mysql
user. Substitute your own value if needed.
UPDATE (to support non-sudo-priviledged users)
To get a bash script to change-users without sudu
would be to require the ability to suid
("switch user id"). This, as pointed out by this answer, is a security restriction that requires a hack to work around. Check this blog for an example of "how to" work around it (I haven't tested/tried it, so I can't confirm it's success).
My recommendation, if possible, would be to write a script in C that is given permission to suid (try chmod 4755 file-name
). Then, you can call setuid(#)
from the C script to set the current user's id and either continue code-execution from the C application, or have it execute a separate bash script that runs whatever commands you need/want. This is also a pretty hacky method, but as far as non-sudo alternatives it's probably one of the easiest (in my opinion).
That could do the test:
if read -a dirVals < <(stat -Lc "%U %G %A" $directory) && (
( [ "$dirVals" == "$wantedUser" ] && [ "${dirVals[2]:2:1}" == "w" ] ) ||
( [ "${dirVals[2]:8:1}" == "w" ] ) ||
( [ "${dirVals[2]:5:1}" == "w" ] && (
gMember=($(groups $wantedUser)) &&
[[ "${gMember[*]:2}" =~ ^(.* |)${dirVals[1]}( .*|)$ ]]
) ) )
then
echo 'Happy new year!!!'
fi
Explanations:
There is only one test (if), no loop and no fork.
+ Nota: as I'v used stat -Lc
instead of stat -c
, this will work for symlinks too!
So condition is if,
- I could successfully read stats of
$directory
and assign them todirVals
, - And (
- ( Owner match And Flag UserWriteable is present )
- or flag Other Writeable is present
- or ( Flag GroupWriteabe is present AND
- I could successfully assing member list of
$wantedUser
togMember
AND - A string built by merging fields 2 to last of
$gMember
will match beginOfSting-Or-something-followed-by-a-space, immediately followed by target's group (${dirVals[1]}
), immediately followed by a-space-followed-by-something-Or-endOfString. )
- I could successfully assing member list of
then echo Happy new year!
As the group's test implie a second fork (And I love to reduce as possible such calls), this is the last test to be done.
Old:
Simply:
su - mysql -c "test -w '$directory'" && echo yes
yes
or:
if su - mysql -s /bin/sh -c "test -w '$directory'" ; then
echo 'Eureka!'
fi
Nota: Warn to enclose first with double-quotes for having $directory
developped!