I was solving a problem for Testling and run into difficulties. Every Github user who signs up for Testling gets a Unix user on Testling's servers. When they commit code, we get a Github hook, switch to their user, clone their repo, and run their tests.

Since we're releasing early and releasing often, and following worse is better philosophy, we simply setup a chroot environment for all Testling users, then we just chroot to this environment, and su - username and run scripts that run the tests all with a single command:

sudo chroot /chroot su - user -c "cmd args"

This is pretty nifty! This command combines four commands together - sudo, chroot, su and cmd. Each command calls the next one and when the last command finishes we return back to our shell and exit (instead of being left inside of chroot in an interactive shell mode as user.)

Now here's the problem I run into. For some reason user's environment never got executed. Our test runners setup PATHs and other customizations but .bash_profile was never ran.

The solution that I found was to run .bash_profile yourself, like this:

sudo chroot /chroot su - user -c ". ~/.bash_profile; cmd args"

This does what I wanted and runs cmd args inside of chroot located at /chroot as user, initializes .bash_profile and after running cmd it quits.

On Sourcing ~/.bash_profile

Some people couldn't understand why I was sourcing ~/.bash_profile. The reason is because I was unable to find another way to invoke the login shell and have the environment initialized.

I tried this:

sudo chroot /chroot su -l user -c "cmd args"

And it just wouldn't execute ~/.bash_profile. Su's man page says that -l invokes a login shell, however I don't see that happening. Here's the transcript that demonstrates it:

pkrumins$ pwd

pkrumins$ sudo cat ./home/testuser1/.bash_profile 
echo "moo"

pkrumins$ sudo chroot /chroot su -l testuser1 -c 'echo $PATH'

pkrumins$ sudo chroot /chroot su -l testuser1 

testuser1$ echo $PATH

testuser1$ ^D

pkrumins$ sudo chroot /chroot su -l testuser1 -c '. ~/.bash_profile; echo $PATH'       

pkrumins$ sudo chroot /chroot su -l testuser1

testuser1$ /bin/bash -l -c 'echo $PATH'

This definitely looks like a bug in su to me when -l and -c are used together. So the only way to get the environment loaded is by sourcing the initialization files yourself.


I went through su's source code (can be found in util-linux package) and it turns out it's not a bug in su!

Here are the arguments that su passes to execv when I run su user -c 'echo $PATH' (see run_shell function in su.c):

shell: /bin/bash
args[0]: -bash
args[1]: -c
args[2]: echo $PATH

As you can see, args[0][0] is -, which makes bash a login shell, so bash should be executing the startup files, but it does not!

To figure out what was happening, I built a custom version of bash and added a bunch of debugging statements. I found that there are two different login-shell states! Who'd have thought? One that you get when you've set args[0][0] to -, the other that you get when you've used the -l argument (to bash, not su!).

In case args[0][0] is -, and you're using -c to execute code, bash will not execute startup files because it's in this "non-interactive positive-login-shell" state (see run_startup_files function in bash's shell.c source file.)

However, if you use -l and -c, it goes into "non-interactive negative-login-shell" state, and it will execute startup files. But we can't pass -l argument to bash, we can only pass it to su, which is not the same as passing it to bash!

That was an exciting adventure debugging this. If you want bash to execute startup files through su, use the . ~/.bash_profile sourcing trick! (Or alternatively define NON_INTERACTIVE_LOGIN_SHELLS when building bash.)

Until next time!


lava Permalink
December 24, 2012, 16:58

> su - user sh -c ". ~/.profile; command args"

... I have no words.

su user -c ". ~/.profile; command args"

December 24, 2012, 20:00

Oh well, I missed that "-c" calls the commands via shell.

wjgeorge Permalink
December 25, 2012, 17:06

well fix the code in the article.

not sure why you're just not doing a 'sudo -u user cmd'

December 25, 2012, 17:19

Because then the cmd will not run inside of chroot.

December 25, 2012, 05:39

You do great work Peter. I appreciate every post you have on your site. Keep up the great work and thanks for sharing.

December 25, 2012, 14:17

Thank you!

Sharif Olorin Permalink
December 26, 2012, 00:04

A login shell should source ~/.bashrc, to the best of my knowledge and experience. What environment are you using/what specifically in your .bashrc are you noting doesn't get executed?

yrougy Permalink
December 26, 2012, 09:26

Nope ! The behavior is:
Login shell -> profile
Non-login shell -> ressource file (.bashrc)

It's either one or the other.

However, most distros puts a profile with something like

if [ -f ~/.bashrc ]
. ~/bashrc

which "manually" source the rc file if it exists. This is the best way to keep things proper (environment goes to profile and variable non exported to the ressource file this way, so aliases or PS1 goes to the rc file and are usable even in a login shell).


December 27, 2012, 18:41

It's a bug in su. I just updated the article at the that demonstrates it.

Update: it's actually not a bug in su. Check out the last update.

yrougy Permalink
December 26, 2012, 09:21

You have to source .bashrc because, in case of a login shell, the bash process only loads the profile (/etc/profile, ~/.profile and/or ~/.bash_profile ) . The *profile may loads the ressource file by itself, but it's up to you to do so.

In case of a non-interactive shell, the bash process *doesn't* look for the ~/.bashrc specifically. Actually it searches for a environment variable BASH_ENV to know the ressource file to loads.


December 27, 2012, 19:59

That's right. However I discovered it was a bug in su. If you use su with -c and -l, it will not spawn the shell as a login shell.

Update: it's actually not a bug in su. Check out the last update.

Maxim Yegorushkin Permalink
December 26, 2012, 20:06

The way you pass commands to the shell may require akward quoting. Just pipe the commands into the shell. See http://stackoverflow.com/a/3435460/412080

jaysunn Permalink
January 01, 2013, 14:02

I really enjoy your posts? Please do not let the trolls bite.. Keep the great tips coming.

Happy New Year!!!

January 02, 2013, 23:08

Happy new year!

January 07, 2013, 10:38

but what's the difference of positive-login-shell and negative-login-shell?

January 08, 2013, 13:36

Peter Krumins,
I really appreciate your posts. Please do not give up listening to those dumb people. Keep on posting.
Happy New Year.

sapphirecat Permalink
April 11, 2013, 18:35

Random thought: if your working directory is already /chroot, you can use the current-dir syntax to expand the path: sudo chroot ~+ su ....

Manh Cuong Le Permalink
May 15, 2013, 15:30

Hi, Peter Krumins.

This is a very nice article. But I have trouble in understanding what is "positive-login-shell" and "negative-login-shell".

Please give it more details.

poppinfresh Permalink
August 26, 2013, 21:14

This is not terribly safe and negates the purpose of using chroot. You want to remove all setuid binaries and interactive shells from a chroot environment.

Leave a new comment

(why do I need your e-mail?)

(Your twitter handle, if you have one.)

Type the word "antispam_365": (just to make sure you're a human)

Please preview the comment before submitting to make sure it's OK.