Confusion with JEXEC and Pipes on the Command Line

Jul 2, 2024 · 742 words · 4 minute read

I've been learning more about FreeBSD's jails while trying to set up a home server. One point that confused me was the handling of pipes with jexec(8), and thought I should write that down before it confuses me again in 6 months time and I've forgotten what the solution is.

Short Answer

When using pipes with jexec, you should wrap your commands in a string with single-quotes and pass them to a shell you call with jexec, e.g. the following command run from within the jail will create a file called 'result.txt' containing the number "4". (In your current directory, which in all examples we'll assume is the root directory for the jail.)

echo 2+2 | bc > result.txt

Has to become 1:

jexec jailname sh -c 'echo 2+2 | bc > result.txt'

The following doesn't work:

jexec jailname echo 2+2 | bc > result.txt

This leaves the 'result.txt' file in whichever directory on the jailhost you're running the command from. This is because each process in the pipeline is executed in it's own subshell, and the jexec command will only run the first command in the jail, the part after the pipe, is running not in the jail, but on the host.

How I Got There

I've been working on creating a samba share inside a jail, but set up via an Ansible playbook2, which means I've been scripting a lot of jexec based commands to set things up from outside the jail.

Where I got stuck was as follows. I wanted to scrip the creation of samba users using pdbedit(8), which normally asks for a password at a prompt, but you can pipe them in as a string using the -t (--password-from-stdin) flag. Running as root inside the jail, this works:

echo -e "password\npassword" | pdbedit -a -t -u username

(Note: This is using the sh(1) built in echo with -e for C like escape sequences, and not /bin/echo (echo(1)) that doesn't support the -e option. Most shells have an echo as a built in, which will be described in the shell's man page, and not via man echo.)

But when run from the jail host this fails:

doas jexec jailname echo -e "password\npassword" | pdbedit -a -t -u username
Can't load /usr/local/etc/smb4.conf - run testparm to debug it
Failed to read passwords.

Which confused me no end. Why was this unable to find the Samba configuration file smb4.conf, which did exist in the jail?

After more head scratching than it should have been this FreeBSD Forum post on jexec and scripting with pipes (from 11 years ago!) finally gave me the clue I needed to understand what was happening.

I'd been confused by having installed samba also on the jail host, but it wasn't currently set up, which is why it could still find pdbedit, but then not create a user, as there wasn't a configuration file in place, but if didn't give me a not found error for a program that wasn't installed.

What's Happening

The following also creates the 'results.txt' file on the jail host, and not in the jail:

jexec jailname echo 2+2 | jexec jailname bc > result.txt

This is because the control operators, | & > >> etc. are not programs run separately from the shell, but commands within the shell that capture, and redirect the stdout, more explicitly:

 jexec jailname  echo 2+2  |  jexec jailname  bc  > result.txt 

Where:

  • Red executes on the jail host
  • Blue executes in the jail
  • Purple is shell functionality for redirecting stdout that also runs outside the jail

This means it's not possible to run the 'shell internal' controls in the jail. These were simplified examples, and if you wanted to write the stdout to a file in the jail you could use jexec with tee(1), which can be run in the jail as it's not part of the shell.

So, thanks to some nice people who responded to my post, this confusion has become a bit of a teachable moment for my understanding of shells.


Update 2024-07-04: Added feedback I got from my posting to make clear what's happening.


1

You may need to prefix this with sudo or doas depending if your user is allowed to run commands in a jail, or run as root.

2

This may be a slightly cursed way of using Ansible with jails, and I'll probably try to explained in a future blog post, once I get it working.