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