]> CyberLeo.Net >> Repos - CDN/j.git/blob - j
j: realpath does not always exist; shift does not always succeed with no args
[CDN/j.git] / j
1 #!/bin/sh
2 # Copyright 2011 CyberLeo, All Rights Reserved
3 # http://wiki.cyberleo.net/wiki/CyberLeo/COPYRIGHT
4
5 : ${ORIG_USER=$(id -un)}
6
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}" "${@}"
9
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; }
13 pebkac() {
14   [ "${*}" ] && printf "%s\n\n" "${*}"
15   cat <<EOF
16 Usage:
17   <command> <name> [arguments]
18   list
19   ls      list available chroots
20
21   status  show jail status
22
23   start   prepare an existing chroot for use
24
25   stop    undo what 'start' did
26
27   kill    Forcefully kill all processes; only applicable with cgroups
28
29   enter
30   shell   spawn a shell or command within the chroot
31
32   eval    evaluate a shell command line within the chroot
33
34   mount   mount chroot filesystems according to chroot fstab
35
36   umount  umount chroot filesystems that were mounted with 'mount'
37
38   init <dir> <seed> [seed-specific options]
39           initialize a chroot using the specified seed in seeds/
40
41 EOF
42   exit 1
43 }
44
45 cmd="$(basename "${0}")"
46 j="$(readlink -f "$(dirname "${0}")")"
47 jarch="${J_ARCH:-$(uname -m)}"
48 jbase="${J_BASE:-$(readlink -f "${j}/../")}"
49 jseed="${j}/seeds"
50 jname="${J_NAME:-$(basename "${1}")}" #"
51 juser="${J_USER}"
52
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
55 do
56   [ -f "${chroot}" ] && break
57   unset chroot
58 done
59 [ "${chroot}" ] || wtf "Cannot find chroot in /usr/sbin:/usr/bin:/sbin:/bin"
60
61 # cgroup to use; if not found, cgroup support will not be used
62 jcgroup="freezer"
63 # cgroup parent to use (${cgroup}/${parent}/${jname})
64 jcgparent="j"
65
66 # Remove chroot name from argument stack, if passed in
67 [ "${J_NAME}" ] || shift && true
68
69 # Propagate certain environment variables; sterilize the rest of the environment
70 jenv="
71   LANG=${LANG}
72   TERM=${TERM}
73   USER=${USER}
74 "
75
76 # Create a new chroot, using a seed script in seeds/
77 j_init() {
78   # Make sure this does NOT exist
79   jname="${1}"; shift
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"
85   jroot="${jdir}/root"
86   set -e
87   . "${jbase}/j/seeds/${seed}.sh" "${@}"
88 }
89
90 # Figure out and set chroot parameters; needed for all functions that follow
91 j_params() {
92   ( jname="${1:-jname}"
93
94     # Make sure jname is not empty
95     if [ -z "${jname}" ]
96     then
97       printf "jerror='%s'\n" "jname empty"
98       return 1
99     fi
100
101     # Given a chroot name, find and set up the chroot dir
102     jdir="${jbase}/${jname}"
103     jroot="${jdir}/root"
104     if [ ! -d "${jdir}" -o ! -d "${jroot}" ]
105     then
106       printf "jerror='%s'\n" "not a directory"
107       return 1
108     fi
109
110     # Does this have a valid fstab file?
111     [ -f "${jdir}/fstab" ] && jfstab="${jdir}/fstab"
112
113     # Where is the shell?
114     jshell=""
115     for shell in /bin/bash /usr/bin/bash /usr/local/bin/bash /bin/sh
116     do
117       if [ -f "${jroot}/${shell}" ]
118       then
119         jshell=${shell}
120         break
121       fi
122     done
123     if [ -z "${jshell}" ]
124     then
125       printf "jerror='%s'\n" "unable to locate usable shell; is this a jail?"
126       return 1
127     fi
128
129     printf "jerror='' jname='%s' jdir='%s' jroot='%s' jfstab='%s' jshell='%s'\n" "${jname}" "${jdir}" "${jroot}" "${jfstab}" "${jshell}"
130   )
131 }
132
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
137   do
138     [ "${type}" = "cgroup" ] || continue
139     oldifs="${IFS}"
140     IFS=','
141     set -- ${opts}
142     IFS=${oldifs}
143     while [ "${1}" ]
144     do
145       [ "${1}" = "${cgroup}" ] && echo "${dir}" && break 2
146       shift
147     done
148   done
149 }
150
151 # Cache and expose cgroup mountpoint for given chroot; make sure it exists
152 j_cgroup() {
153   [ "${jcg}" ] && { echo "${jcg}"; return; }
154   jcg="$(j_locate_cgroup_mount "${cgroup}")/${jcgparent}/${jname}"
155   mkdir -p "${jcg}"
156   echo "${jcg}"
157 }
158
159 j_cg_trap() {
160   [ "$(j_cgroup)" ] || return
161   while [ "${1}" ]
162   do
163     echo "${1}" > "$(j_cgroup)/tasks"
164     shift
165   done
166 }
167
168 # Run a command within a cgroup, if available
169 j_cg_eval() {
170   j_cg="$(j_cgroup)"
171   if [ "$(j_cgroup)" ]
172   then
173     sh -ex <<EOF
174 echo $$ > "$(j_cgroup)/tasks"
175 eval "${*}"
176 EOF
177   else
178     eval "${*}"
179   fi
180 }
181
182 # Copy ipcc into the jail and add helpful symlinks, so it can communicate
183 # simple commands to the outside
184 j_ipc_setup() {
185   ipcc="$(dirname "${0}")/ipcc"
186   cp -f "${ipcc}" "${jroot}/bin"
187   ( cd "${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
192   )
193 }
194
195 # Launch ipcd on jail
196 j_ipc_start() {
197   ipcd_pid="${jdir}/ipcd.pid"
198   # Kill any running IPCd; remove stale pid file and socket
199   if [ -f "${ipcd_pid}" ]
200   then
201     pid="$(cat "${ipcd_pid}")"
202     if [ "${pid}" ]
203     then
204       if ps ouser=,command= "${pid}" | grep "^${ORIG_USER}" | grep -q "ipcd"
205       then
206         omg "IPCd: already running! Killing"
207         kill -TERM "${pid}"
208       fi
209       omg "IPCd: removing stale pid file"
210       rm -f "${ipcd_pid}" "${jroot}/tmp/jipc"
211     fi
212   fi
213   su "${ORIG_USER}" "${jbase}/j/ipcd" "${jdir}" &
214   pid="${!}"
215   echo "${pid}" > "${ipcd_pid}"
216   j_cg_trap "${pid}"
217 }
218
219 # Stop ipcd on jail
220 j_ipc_stop() {
221   ipcd_pid="${jdir}/ipcd.pid"
222   [ -f "${ipcd_pid}" ] && pid="$(cat "${ipcd_pid}")"
223   [ "${pid}" ] && kill -TERM ${pid}
224   rm -f "${ipcd_pid}"
225 }
226
227 # Is this a chroot?
228 j_is() {
229   eval $(j_params "${1}")
230   [ "${jerror}" ] && return 1 || return 0
231 }
232
233 # List available chroots
234 j_ls() {
235   ( cd "${jbase}"; ls -1 ) | while read jname
236   do
237     j_is "${jname}" && echo "${jname}"
238   done
239 }
240
241 # Chroot is 'up' if /dev/pts and /proc are mounted
242 j_up() {
243   jname="${1:-${jname}}"
244   eval "$(j_params "${jname}")"
245   [ "${jerror}" ] && wtf "${jerror}"
246   grep -q "^[^ ]* ${jroot}/dev/pts devpts" /proc/mounts || return 1
247   grep -q "^[^ ]* ${jroot}/proc proc" /proc/mounts || return 1
248   return 0
249 }
250
251 # Poll chroot status (j_up)
252 j_status() {
253   [ -z "${1}" ] && set - $(j_ls)
254   while [ "${1}" ]
255   do
256     j_up "${1}" && meh "$(printf '\033[1;32mup\033[0m')" || meh "$(printf '\033[1;31mdown\033[0m')"
257     shift
258   done
259 }
260
261 # Copy a mount from the host (for things like /dev, /proc, /sys, et alia)
262 j_fstab_copy_mount() {
263   grep -Eq "${1}[[:space:]]" "${tmp_fstab}" || grep -E \
264     "[[:space:]]${1}[[:space:]]" /proc/mounts | head -n 1 >> "${tmp_fstab}"
265 }
266
267 # Prepare fstab for use:
268 # * Strip comments
269 # * Make sure certain required mounts exist (/dev/pts, /proc)
270 # * Sort by mountpoint, to ensure mounts are performed in the correct order
271 j_fstab_synthesize() {
272   local tmp_fstab="$(mktemp "${jdir}/.fstab.XXXXXXXX")"
273   [ -f "${jfstab}" ] && sed -e 's/#.*$//; /^\W*$/d' "${jfstab}" > "${tmp_fstab}"
274   j_fstab_copy_mount "/dev/pts"
275   j_fstab_copy_mount "/proc"
276   sort -k2 "${tmp_fstab}"
277   rm -f "${tmp_fstab}"
278 }
279
280 # Process fstab file and mount the filesystems therein; if no fstab file (or if
281 # excludes certain critical filesystems) then augment it. This will also maintain
282 # an mtab file so they can be unmounted later (and so df works inside chroot).
283 #
284 # All mountpoints listed in this file will be prepended with ${jroot} prior to
285 # their being mounted or umounted! Make sure the fstab mountpoints are relative
286 # to the chroot root!
287 j_fstab_mount() {
288   echo "rootfs / rootfs rw 0 0" > "${jdir}/mtab"
289   j_fstab_synthesize | while read src dir type opts fsck dump
290   do
291     mkdir -p "${dir}" || break
292     mount -t "${type}" -o "${opts}" "${src}" "${jroot}/${dir}" || break
293     printf "%s %s %s %s 0 0\n" "${src}" "/${dir##/}" "${type}" "${opts}" >> "${jdir}/mtab"
294   done
295 }
296
297 # Process fstab and mtab files and unmount the filesystems therein (opposite j_fstab_mount)
298 j_fstab_umount() {
299   [ -s "${jdir}/mtab" ] || j_fstab_synthesize > "${jdir}/mtab"
300   tac "${jdir}/mtab" | while read src dir unused
301   do
302     # Mounted?
303     grep -Eq "[[:space:]]${jroot}${dir%%/}[[:space:]]" /proc/mounts || continue
304     umount -f "${jroot}/${dir}" || umount -l "${jroot}/${dir}" || break
305   done
306   :>"${jdir}/mtab"
307 }
308
309 # Start up chroot
310 j_start() {
311   jname="${1:-${jname}}"
312   j_up "${jname}" && return 0
313   eval "$(j_params "${jname}")"
314   meh "starting ${jname} ..."
315
316   # Mount filesystems and copy in /etc/mtab
317   j_fstab_mount
318   cp "${jdir}/mtab" "${jroot}/etc/mtab"
319
320   # Copy in ipcc and launch ipcd
321   j_ipc_setup
322   j_ipc_start
323
324   # Start all services in /etc/rcJ.d
325   [ -d "${jroot}/etc/rcJ.d" ] || return 0
326   j_root_eval "${jname}" '( ls -1 /etc/rcJ.d/* 2>&- | grep /S | sort | sed -e "s/$/ start/" | sh )'
327 }
328
329 # Execute command in chroot as root
330 j_root_eval() {
331   jname="${1:-${jname}}"; shift
332   j_up "${jname}" || wtf "chroot not running"
333   eval "$(j_params "${jname}")"
334   j_cg_trap $$
335   env -i ${jenv} "${chroot}" "${jroot}" /bin/sh -c "${*}"
336 }
337
338 # Execute command in chroot
339 j_eval() {
340   jname="${1:-${jname}}"; shift
341   j_up "${jname}" || wtf "chroot not running"
342   eval "$(j_params "${jname}")"
343   j_cg_trap $$
344   env -i ${jenv} "${chroot}" "${jroot}" /bin/su "${juser:-${USER}}" -c "${*}"
345 }
346
347 # Invoke a shell within the chroot
348 j_shell() {
349   jname="${1:-${jname}}"
350   eval "$(j_params "${jname}")"
351   j_eval "${jname}" "cd; exec ${jshell} -l"
352 }
353
354 # Shut down chroot
355 j_stop() {
356   jname="${1:-${jname}}"
357   eval "$(j_params "${jname}")"
358   j_up "${jname}" || return 0
359   meh "stopping ${jname} ..."
360
361   # Stop all services in /etc/rcJ.d
362   j_root_eval "${jname}" '[ -d /etc/rcJ.d ] && ( ls -1 /etc/rcJ.d/* 2>&- | grep /S | sort -r | sed -e "s/$/ stop/" | sh )'
363
364   # Terminate ipcd
365   j_ipc_stop
366
367   # Unmount filesystems
368   j_fstab_umount
369 }
370
371 # Send a signal to all processes inside a chroot, if possible
372 j_signal() {
373   sig="${1:-HUP}"
374   jname="${2:-${jname}}"
375
376   [ "$(j_cgroup)" ] || { echo "No cgroup support; cannot signal"; return 1; }
377   pids="$(cat "$(j_cgroup)/tasks")"
378   if [ "${pids}" ]
379   then
380     echo Sending ${sig} to ${pids} ...
381     echo "FROZEN" > "$(j_cgroup)/freezer.state"
382     kill -${sig} ${pids}
383     echo "THAWED" > "$(j_cgroup)/freezer.state"
384   fi
385 }
386
387 # Kill all processes inside chroot (TERM, then KILL)
388 j_kill() {
389   jname="${1:-${jname}}"
390   eval "$(j_params "${jname}")"
391   meh "killing ${jname} ..."
392
393   j_signal "TERM" "${jname}"
394   sleep 2
395   j_signal "KILL" "${jname}"
396 }
397
398 # Hook to allow mounting chroot mounts from command line
399 j_mount() {
400   jname="${1:-${jname}}"
401   eval "$(j_params "${jname}")"
402   j_fstab_mount
403 }
404
405 # Hook to allow unmounting chroot mounts from command line
406 j_umount() {
407   jname="${1:-${jname}}"
408   eval "$(j_params "${jname}")"
409   j_fstab_umount
410 }
411
412 case "${cmd}" in
413 init|create) j_init "${jname}" "${@}" ;;
414 ls|list) j_ls ;;
415 status) j_status "${jname}" "${@}" ;;
416 start) j_start "${jname}" ;;
417 shell|enter) j_shell "${jname}" ;;
418 eval) j_eval "${jname}" "${*}" ;;
419 stop) j_stop "${jname}" ;;
420 kill) j_kill "${jname}" ;;
421 mount) j_mount "${jname}" ;;
422 umount) j_umount "${jname}" ;;
423 *) pebkac ;;
424 esac