2 # Copyright 2011 CyberLeo, All Rights Reserved
3 # http://wiki.cyberleo.net/wiki/CyberLeo/COPYRIGHT
5 : ${ORIG_USER=$(id -un)}
7 # Need root beyond here
8 [ "$(id -u)" -eq 0 ] || exec sudo env "J_ARCH=${J_ARCH}" "J_BASE=${J_BASE}" "J_NAME=${J_NAME}" "J_USER=${J_USER:-${USER}}" "ORIG_USER=${ORIG_USER}" "DISPLAY=${DISPLAY}" "${0}" "${@}"
10 meh() { printf " \033[1;32m*\033[0m %s%s\n" "${jname:+${jname}: }" "${*}"; }
11 omg() { printf " \033[1;33m*\033[0m %s%s\n" "${jname:+${jname}: }" "${*}"; }
12 wtf() { printf " \033[1;31m*\033[0m %s%s\n" "${jname:+${jname}: }" "${*}"; exit 1; }
14 [ "${*}" ] && printf "%s\n\n" "${*}"
17 <command> <name> [arguments]
19 ls list available chroots
21 status show jail status
23 start prepare an existing chroot for use
25 stop undo what 'start' did
27 kill Forcefully kill all processes; only applicable with cgroups
30 shell spawn a shell or command within the chroot
32 eval evaluate a shell command line within the chroot
34 mount mount chroot filesystems according to chroot fstab
36 umount umount chroot filesystems that were mounted with 'mount'
38 init <dir> <seed> [seed-specific options]
39 initialize a chroot using the specified seed in seeds/
45 cmd="$(basename "${0}")"
46 j="$(readlink -f "$(dirname "${0}")")"
47 jarch="${J_ARCH:-$(uname -m)}"
48 jbase="${J_BASE:-$(readlink -f "${j}/../")}"
50 jname="${J_NAME:-$(basename "${1}")}" #"
53 # Find chroot binary, since it can be in many different places on different distros
54 for chroot in /usr/sbin/chroot /usr/bin/chroot /sbin/chroot /bin/chroot
56 [ -f "${chroot}" ] && break
59 [ "${chroot}" ] || wtf "Cannot find chroot in /usr/sbin:/usr/bin:/sbin:/bin"
61 # cgroup to use; if not found, cgroup support will not be used
63 # cgroup parent to use (${cgroup}/${parent}/${jname})
66 # Remove chroot name from argument stack, if passed in
67 [ "${J_NAME}" ] || shift && true
69 # Propagate certain environment variables; sterilize the rest of the environment
76 # Create a new chroot, using a seed script in seeds/
78 # Make sure this does NOT exist
80 [ "${jname}" ] || pebkac "j_init: no name provided"
81 seed="$(echo "${1}" | tr 'A-Z' 'a-z')"; shift
82 [ -f "${jbase}/j/seeds/${seed}.sh" ] || pebkac "Unknown seed '${seed}'"
83 jdir="${jbase}/${jname}"
84 [ -d "${jdir}" ] && pebkac "j_init: ${jname} already exists"
87 . "${jbase}/j/seeds/${seed}.sh" "${@}"
90 # Figure out and set chroot parameters; needed for all functions that follow
94 # Make sure jname is not empty
97 printf "jerror='%s'\n" "jname empty"
101 # Given a chroot name, find and set up the chroot dir
102 jdir="${jbase}/${jname}"
104 if [ ! -d "${jdir}" -o ! -d "${jroot}" ]
106 printf "jerror='%s'\n" "not a directory"
110 # Does this have a valid fstab file?
111 [ -f "${jdir}/fstab" ] && jfstab="${jdir}/fstab"
113 # Where is the shell?
115 for shell in /bin/bash /usr/bin/bash /usr/local/bin/bash /bin/sh
117 if [ -f "${jroot}/${shell}" ]
123 if [ -z "${jshell}" ]
125 printf "jerror='%s'\n" "unable to locate usable shell; is this a jail?"
129 printf "jerror='' jname='%s' jdir='%s' jroot='%s' jfstab='%s' jshell='%s'\n" "${jname}" "${jdir}" "${jroot}" "${jfstab}" "${jshell}"
133 # Find the 'freezer' cgroup, to determine whether the chroot can be further isolated
134 j_locate_cgroup_mount() {
135 cgroup="${1:-freezer}"
136 cat /proc/mounts | while read src dir type opts fsck dump extra
138 [ "${type}" = "cgroup" ] || continue
145 [ "${1}" = "${cgroup}" ] && echo "${dir}" && break 2
151 # Cache and expose cgroup mountpoint for given chroot; make sure it exists
153 [ "${jcg}" ] && { echo "${jcg}"; return; }
154 jcg="$(j_locate_cgroup_mount "${cgroup}")/${jcgparent}/${jname}"
160 [ "$(j_cgroup)" ] || return
163 echo "${1}" > "$(j_cgroup)/tasks"
168 # Run a command within a cgroup, if available
174 echo $$ > "$(j_cgroup)/tasks"
182 # Copy ipcc into the jail and add helpful symlinks, so it can communicate
183 # simple commands to the outside
185 ipcc="$(dirname "${0}")/ipcc"
186 cp -f "${ipcc}" "${jroot}/bin"
188 [ -e ee ] || ln -s ipcc ee
189 [ -e ff ] || ln -s ipcc ff
190 [ -e gitk ] || ln -s ipcc gitk
191 [ -e gitka ] || ln -s ipcc gitka
195 # Launch ipcd on jail
197 ipcd_pid="${jdir}/ipcd.pid"
198 # Kill any running IPCd; remove stale pid file and socket
199 if [ -f "${ipcd_pid}" ]
201 pid="$(cat "${ipcd_pid}")"
204 if ps ouser=,command= "${pid}" | grep "^${ORIG_USER}" | grep -q "ipcd"
206 omg "IPCd: already running! Killing"
209 omg "IPCd: removing stale pid file"
210 rm -f "${ipcd_pid}" "${jroot}/tmp/jipc"
213 [ -e "${jroot}/tmp/jipc" ] && {
214 omg "IPCd: not running, but pipe exists; removing"
215 rm -f "${jroot}/tmp/jipc"
218 su "${ORIG_USER}" "${jbase}/j/ipcd" "${jdir}" &
220 echo "${pid}" > "${ipcd_pid}"
226 ipcd_pid="${jdir}/ipcd.pid"
227 [ -f "${ipcd_pid}" ] && pid="$(cat "${ipcd_pid}")"
228 [ "${pid}" ] && kill -TERM ${pid}
234 eval $(j_params "${1}")
235 [ "${jerror}" ] && return 1 || return 0
238 # List available chroots
240 ( cd "${jbase}"; ls -1 ) | while read jname
242 j_is "${jname}" && echo "${jname}"
246 # Chroot is 'up' if /dev/pts and /proc are mounted
248 jname="${1:-${jname}}"
249 eval "$(j_params "${jname}")"
250 [ "${jerror}" ] && wtf "${jerror}"
251 grep -q "^[^ ]* ${jroot}/dev/pts devpts" /proc/mounts || return 1
252 grep -q "^[^ ]* ${jroot}/proc proc" /proc/mounts || return 1
256 # Poll chroot status (j_up)
258 [ -z "${1}" ] && set - $(j_ls)
261 j_up "${1}" && meh "$(printf '\033[1;32mup\033[0m')" || meh "$(printf '\033[1;31mdown\033[0m')"
266 # Copy a mount from the host (for things like /dev, /proc, /sys, et alia)
267 j_fstab_copy_mount() {
268 grep -Eq "${1}[[:space:]]" "${tmp_fstab}" || grep -E \
269 "[[:space:]]${1}[[:space:]]" /proc/mounts | head -n 1 >> "${tmp_fstab}"
272 # Prepare fstab for use:
274 # * Make sure certain required mounts exist (/dev/pts, /proc)
275 # * Sort by mountpoint, to ensure mounts are performed in the correct order
276 j_fstab_synthesize() {
277 local tmp_fstab="$(mktemp "${jdir}/.fstab.XXXXXXXX")"
278 [ -f "${jfstab}" ] && sed -e 's/#.*$//; /^\W*$/d' "${jfstab}" > "${tmp_fstab}"
279 j_fstab_copy_mount "/dev/pts"
280 j_fstab_copy_mount "/proc"
281 sort -k2 "${tmp_fstab}"
285 # Process fstab file and mount the filesystems therein; if no fstab file (or if
286 # excludes certain critical filesystems) then augment it. This will also maintain
287 # an mtab file so they can be unmounted later (and so df works inside chroot).
289 # All mountpoints listed in this file will be prepended with ${jroot} prior to
290 # their being mounted or umounted! Make sure the fstab mountpoints are relative
291 # to the chroot root!
293 echo "rootfs / rootfs rw 0 0" > "${jdir}/mtab"
294 j_fstab_synthesize | while read src dir type opts fsck dump
296 mkdir -p "${dir}" || break
297 mount -t "${type}" -o "${opts}" "${src}" "${jroot}/${dir}" || break
298 printf "%s %s %s %s 0 0\n" "${src}" "/${dir##/}" "${type}" "${opts}" >> "${jdir}/mtab"
302 # Process fstab and mtab files and unmount the filesystems therein (opposite j_fstab_mount)
304 [ -s "${jdir}/mtab" ] || j_fstab_synthesize > "${jdir}/mtab"
305 tac "${jdir}/mtab" | while read src dir unused
308 grep -Eq "[[:space:]]${jroot}${dir%%/}[[:space:]]" /proc/mounts || continue
309 umount -f "${jroot}/${dir}" || umount -l "${jroot}/${dir}" || break
316 jname="${1:-${jname}}"
317 j_up "${jname}" && return 0
318 eval "$(j_params "${jname}")"
319 meh "starting ${jname} ..."
321 # Mount filesystems and copy in /etc/mtab
323 cp "${jdir}/mtab" "${jroot}/etc/mtab"
325 # Copy in ipcc and launch ipcd
329 # Start all services in /etc/rcJ.d
330 [ -d "${jroot}/etc/rcJ.d" ] || return 0
331 j_root_eval "${jname}" '( ls -1 /etc/rcJ.d/* 2>&- | grep /S | sort | sed -e "s/$/ start/" | sh )'
334 # Execute command in chroot as root
336 jname="${1:-${jname}}"; shift
337 j_up "${jname}" || wtf "chroot not running"
338 eval "$(j_params "${jname}")"
340 env -i ${jenv} "${chroot}" "${jroot}" /bin/sh -c "${*}"
343 # Execute command in chroot
345 jname="${1:-${jname}}"; shift
346 j_up "${jname}" || wtf "chroot not running"
347 eval "$(j_params "${jname}")"
349 env -i ${jenv} "${chroot}" "${jroot}" /bin/su "${juser:-${USER}}" -c "${*}"
352 # Invoke a shell within the chroot
354 jname="${1:-${jname}}"
355 eval "$(j_params "${jname}")"
356 j_eval "${jname}" "cd; exec ${jshell} -l"
361 jname="${1:-${jname}}"
362 eval "$(j_params "${jname}")"
363 j_up "${jname}" || return 0
364 meh "stopping ${jname} ..."
366 # Stop all services in /etc/rcJ.d
367 j_root_eval "${jname}" '[ -d /etc/rcJ.d ] && ( ls -1 /etc/rcJ.d/* 2>&- | grep /S | sort -r | sed -e "s/$/ stop/" | sh )'
372 # Unmount filesystems
376 # Send a signal to all processes inside a chroot, if possible
379 jname="${2:-${jname}}"
381 [ "$(j_cgroup)" ] || { echo "No cgroup support; cannot signal"; return 1; }
382 pids="$(cat "$(j_cgroup)/tasks")"
385 echo Sending ${sig} to ${pids} ...
386 echo "FROZEN" > "$(j_cgroup)/freezer.state"
388 echo "THAWED" > "$(j_cgroup)/freezer.state"
392 # Kill all processes inside chroot (TERM, then KILL)
394 jname="${1:-${jname}}"
395 eval "$(j_params "${jname}")"
396 meh "killing ${jname} ..."
398 j_signal "TERM" "${jname}"
400 j_signal "KILL" "${jname}"
403 # Hook to allow mounting chroot mounts from command line
405 jname="${1:-${jname}}"
406 eval "$(j_params "${jname}")"
410 # Hook to allow unmounting chroot mounts from command line
412 jname="${1:-${jname}}"
413 eval "$(j_params "${jname}")"
418 init|create) j_init "${jname}" "${@}" ;;
420 status) j_status "${jname}" "${@}" ;;
421 start) j_start "${jname}" ;;
422 shell|enter) j_shell "${jname}" ;;
423 eval) j_eval "${jname}" "${*}" ;;
424 stop) j_stop "${jname}" ;;
425 kill) j_kill "${jname}" ;;
426 mount) j_mount "${jname}" ;;
427 umount) j_umount "${jname}" ;;