]> CyberLeo.Net >> Repos - CDN/j.git/blob - j
j: remove stale jipc pipe on startup, if jipc is not running
[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   else
213     [ -e "${jroot}/tmp/jipc" ] && {
214       omg "IPCd: not running, but pipe exists; removing"
215       rm -f "${jroot}/tmp/jipc"
216     }
217   fi
218   su "${ORIG_USER}" "${jbase}/j/ipcd" "${jdir}" &
219   pid="${!}"
220   echo "${pid}" > "${ipcd_pid}"
221   j_cg_trap "${pid}"
222 }
223
224 # Stop ipcd on jail
225 j_ipc_stop() {
226   ipcd_pid="${jdir}/ipcd.pid"
227   [ -f "${ipcd_pid}" ] && pid="$(cat "${ipcd_pid}")"
228   [ "${pid}" ] && kill -TERM ${pid}
229   rm -f "${ipcd_pid}"
230 }
231
232 # Is this a chroot?
233 j_is() {
234   eval $(j_params "${1}")
235   [ "${jerror}" ] && return 1 || return 0
236 }
237
238 # List available chroots
239 j_ls() {
240   ( cd "${jbase}"; ls -1 ) | while read jname
241   do
242     j_is "${jname}" && echo "${jname}"
243   done
244 }
245
246 # Chroot is 'up' if /dev/pts and /proc are mounted
247 j_up() {
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
253   return 0
254 }
255
256 # Poll chroot status (j_up)
257 j_status() {
258   [ -z "${1}" ] && set - $(j_ls)
259   while [ "${1}" ]
260   do
261     j_up "${1}" && meh "$(printf '\033[1;32mup\033[0m')" || meh "$(printf '\033[1;31mdown\033[0m')"
262     shift
263   done
264 }
265
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}"
270 }
271
272 # Prepare fstab for use:
273 # * Strip comments
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}"
282   rm -f "${tmp_fstab}"
283 }
284
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).
288 #
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!
292 j_fstab_mount() {
293   echo "rootfs / rootfs rw 0 0" > "${jdir}/mtab"
294   j_fstab_synthesize | while read src dir type opts fsck dump
295   do
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"
299   done
300 }
301
302 # Process fstab and mtab files and unmount the filesystems therein (opposite j_fstab_mount)
303 j_fstab_umount() {
304   [ -s "${jdir}/mtab" ] || j_fstab_synthesize > "${jdir}/mtab"
305   tac "${jdir}/mtab" | while read src dir unused
306   do
307     # Mounted?
308     grep -Eq "[[:space:]]${jroot}${dir%%/}[[:space:]]" /proc/mounts || continue
309     umount -f "${jroot}/${dir}" || umount -l "${jroot}/${dir}" || break
310   done
311   :>"${jdir}/mtab"
312 }
313
314 # Start up chroot
315 j_start() {
316   jname="${1:-${jname}}"
317   j_up "${jname}" && return 0
318   eval "$(j_params "${jname}")"
319   meh "starting ${jname} ..."
320
321   # Mount filesystems and copy in /etc/mtab
322   j_fstab_mount
323   cp "${jdir}/mtab" "${jroot}/etc/mtab"
324
325   # Copy in ipcc and launch ipcd
326   j_ipc_setup
327   j_ipc_start
328
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 )'
332 }
333
334 # Execute command in chroot as root
335 j_root_eval() {
336   jname="${1:-${jname}}"; shift
337   j_up "${jname}" || wtf "chroot not running"
338   eval "$(j_params "${jname}")"
339   j_cg_trap $$
340   env -i ${jenv} "${chroot}" "${jroot}" /bin/sh -c "${*}"
341 }
342
343 # Execute command in chroot
344 j_eval() {
345   jname="${1:-${jname}}"; shift
346   j_up "${jname}" || wtf "chroot not running"
347   eval "$(j_params "${jname}")"
348   j_cg_trap $$
349   env -i ${jenv} "${chroot}" "${jroot}" /bin/su "${juser:-${USER}}" -c "${*}"
350 }
351
352 # Invoke a shell within the chroot
353 j_shell() {
354   jname="${1:-${jname}}"
355   eval "$(j_params "${jname}")"
356   j_eval "${jname}" "cd; exec ${jshell} -l"
357 }
358
359 # Shut down chroot
360 j_stop() {
361   jname="${1:-${jname}}"
362   eval "$(j_params "${jname}")"
363   j_up "${jname}" || return 0
364   meh "stopping ${jname} ..."
365
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 )'
368
369   # Terminate ipcd
370   j_ipc_stop
371
372   # Unmount filesystems
373   j_fstab_umount
374 }
375
376 # Send a signal to all processes inside a chroot, if possible
377 j_signal() {
378   sig="${1:-HUP}"
379   jname="${2:-${jname}}"
380
381   [ "$(j_cgroup)" ] || { echo "No cgroup support; cannot signal"; return 1; }
382   pids="$(cat "$(j_cgroup)/tasks")"
383   if [ "${pids}" ]
384   then
385     echo Sending ${sig} to ${pids} ...
386     echo "FROZEN" > "$(j_cgroup)/freezer.state"
387     kill -${sig} ${pids}
388     echo "THAWED" > "$(j_cgroup)/freezer.state"
389   fi
390 }
391
392 # Kill all processes inside chroot (TERM, then KILL)
393 j_kill() {
394   jname="${1:-${jname}}"
395   eval "$(j_params "${jname}")"
396   meh "killing ${jname} ..."
397
398   j_signal "TERM" "${jname}"
399   sleep 2
400   j_signal "KILL" "${jname}"
401 }
402
403 # Hook to allow mounting chroot mounts from command line
404 j_mount() {
405   jname="${1:-${jname}}"
406   eval "$(j_params "${jname}")"
407   j_fstab_mount
408 }
409
410 # Hook to allow unmounting chroot mounts from command line
411 j_umount() {
412   jname="${1:-${jname}}"
413   eval "$(j_params "${jname}")"
414   j_fstab_umount
415 }
416
417 case "${cmd}" in
418 init|create) j_init "${jname}" "${@}" ;;
419 ls|list) j_ls ;;
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}" ;;
428 *) pebkac ;;
429 esac