Wed, 29 Sep 2010

We hit an interesting problem at work recently. A coworker made a deb
package which, during installation, needed to figure out the ID of
the user running it, so it could make files writable by that user.
Of course, while a package is being installed it's run by root,
so the trick is to find out who you were before you sudoed
or sued to root.

He was using the command who am i -- reasonable, since
it's been a staple since the early days of Unix. For those not familiar
with the command, /usr/bin/who, if given two arguments,
regardless of what those arguments are, will print information about
the current logged-in user. It also offers a -m option
to do the same thing. So who am i, who a b,
and who -m should all print a line like:

$ who am i
akkana pts/1 2010-09-29 09:33 (:0.0)

Except they don't. For me, they printed nothing at all -- which broke
my colleague's install script.

A quick poll among friends on IRC showed that who am i
worked for some people, failed for others, with no obvious logic to it.

It's the terminal

It took some digging to find out what was going on, but the difference
turned out to be the terminal being used. The who program
-- with or without -m -- gets its info from /var/run/utmp, a
file that maintains a record of who's logged in to the system.
And it turns out some terminals create a utmp entry, while others don't.
So:

Program

Creates utmp entry?

gnome-terminal

yes

konsole

yes

xterm

no

xfterm4

yes

terminator

no

rxvt

no

roxterm

yes

I use xterm myself. Xterm is documented (in its man page) to modify
the utmp entry, and it has a command-line flat, +ut,
plus two X resources, ptyHandshake and utmpInhibit.
None of the three work: setting

XTerm*ptyHandshake: true
XTerm*utmpInhibit: false

then running xterm +ut still doesn't show up in who.
I guess that's a bug in xterm (or Ubuntu's version of xterm).

How do you get the real user?

Okay, so who am i clearly isn't a reliable way of getting
the user ID. What can you use instead?

Several people suggested the id program. It has a -r
option which supposedly prints the real UID. Unfortunately, what it
really does is print:

$ id -r
id: cannot print only names or real IDs in default format

The man page doesn't offer any suggestions how to use a format other
than default, so we're kinda stuck there.

Update: people keep suggesting id -ru to me.
Evidently I wasn't very clear in this article: the goal is to
get the real id of the login user.
In other words, if you're logged in as mary and using sudo,
you want mary, not root.

Alas, adding -u to id's flags gets only the effective user id: -u
wins over -r. This is very easy to test: sudo id -ru
prints 0, as does id -ru inside su.

But elly on Freenode had a great suggestion:

stat -c '%U' `readlink /proc/self/fd/0`

What does this do?

/proc/self is a symlink to /proc/pid,
a directory where you can find out all sorts of information about
a process.

One of the things you can find out about a process is open file
descriptors: in particular, standard input, output and error.
So /proc/self/fd/0 corresponds to standard input
of the current process -- which in the example above is readlink.

What is readlink? Well, /proc/self/fd/0, in the normal case,
is actually a symlink to the terminal controlling the process.
readlink prints the file to which that link points --
for instance, /dev/pts/1. That's the terminal being used.

Now that we know the name of the terminal, all we need to do is find out
who owns it. (This is the information who am i would have
gotten from utmp, had there been a utmp entry.)
ls -l /dev/pts/1 will show you that it's you, even if you
run it as sudo ls -l /dev/pts/1. You could take that and
strip off fields to get the username, but stat, as elly
suggested, is a much better way of doing that.

Put it all together, and stat -c '%U' `readlink /proc/self/fd/0
gets standard input for the current process, follows the link to get
the controlling terminal, then finds out who owns that terminal.

That's you!

A similar but slightly shorter solution suggested by Mikachu:
stat -c %u `tty`