install-chroot.sh 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888
  1. #!/bin/bash -e
  2. # Copyright (c) 2012 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. # This script installs Debian-derived distributions in a chroot environment.
  6. # It can for example be used to have an accurate 32bit build and test
  7. # environment when otherwise working on a 64bit machine.
  8. # N. B. it is unlikely that this script will ever work on anything other than a
  9. # Debian-derived system.
  10. # Older Debian based systems had both "admin" and "adm" groups, with "admin"
  11. # apparently being used in more places. Newer distributions have standardized
  12. # on just the "adm" group. Check /etc/group for the preferred name of the
  13. # administrator group.
  14. admin=$(grep '^admin:' /etc/group >&/dev/null && echo admin || echo adm)
  15. usage() {
  16. echo "usage: ${0##*/} [-m mirror] [-g group,...] [-s] [-c]"
  17. echo "-b dir additional directories that should be bind mounted,"
  18. echo ' or "NONE".'
  19. echo " Default: if local filesystems present, ask user for help"
  20. echo "-g group,... groups that can use the chroot unauthenticated"
  21. echo " Default: '${admin}' and current user's group ('$(id -gn)')"
  22. echo "-l List all installed chroot environments"
  23. echo "-m mirror an alternate repository mirror for package downloads"
  24. echo "-s configure default deb-srcs"
  25. echo "-c always copy 64bit helper binaries to 32bit chroot"
  26. echo "-h this help message"
  27. }
  28. process_opts() {
  29. local OPTNAME OPTIND OPTERR OPTARG
  30. while getopts ":b:g:lm:sch" OPTNAME; do
  31. case "$OPTNAME" in
  32. b)
  33. if [ "${OPTARG}" = "NONE" -a -z "${bind_mounts}" ]; then
  34. bind_mounts="${OPTARG}"
  35. else
  36. if [ "${bind_mounts}" = "NONE" -o "${OPTARG}" = "${OPTARG#/}" -o \
  37. ! -d "${OPTARG}" ]; then
  38. echo "Invalid -b option(s)"
  39. usage
  40. exit 1
  41. fi
  42. bind_mounts="${bind_mounts}
  43. ${OPTARG} ${OPTARG} none rw,bind 0 0"
  44. fi
  45. ;;
  46. g)
  47. [ -n "${OPTARG}" ] &&
  48. chroot_groups="${chroot_groups}${chroot_groups:+,}${OPTARG}"
  49. ;;
  50. l)
  51. list_all_chroots
  52. exit
  53. ;;
  54. m)
  55. if [ -n "${mirror}" ]; then
  56. echo "You can only specify exactly one mirror location"
  57. usage
  58. exit 1
  59. fi
  60. mirror="$OPTARG"
  61. ;;
  62. s)
  63. add_srcs="y"
  64. ;;
  65. c)
  66. copy_64="y"
  67. ;;
  68. h)
  69. usage
  70. exit 0
  71. ;;
  72. \:)
  73. echo "'-$OPTARG' needs an argument."
  74. usage
  75. exit 1
  76. ;;
  77. *)
  78. echo "invalid command-line option: $OPTARG"
  79. usage
  80. exit 1
  81. ;;
  82. esac
  83. done
  84. if [ $# -ge ${OPTIND} ]; then
  85. eval echo "Unexpected command line argument: \${${OPTIND}}"
  86. usage
  87. exit 1
  88. fi
  89. }
  90. list_all_chroots() {
  91. for i in /var/lib/chroot/*; do
  92. i="${i##*/}"
  93. [ "${i}" = "*" ] && continue
  94. [ -x "/usr/local/bin/${i%bit}" ] || continue
  95. grep -qs "^\[${i%bit}\]\$" /etc/schroot/schroot.conf || continue
  96. [ -r "/etc/schroot/script-${i}" -a \
  97. -r "/etc/schroot/mount-${i}" ] || continue
  98. echo "${i%bit}"
  99. done
  100. }
  101. getkey() {
  102. (
  103. trap 'stty echo -iuclc icanon 2>/dev/null' EXIT INT TERM QUIT HUP
  104. stty -echo iuclc -icanon 2>/dev/null
  105. dd count=1 bs=1 2>/dev/null
  106. )
  107. }
  108. chr() {
  109. printf "\\$(printf '%03o' "$1")"
  110. }
  111. ord() {
  112. printf '%d' $(printf '%c' "$1" | od -tu1 -An)
  113. }
  114. is_network_drive() {
  115. stat -c %T -f "$1/" 2>/dev/null |
  116. egrep -qs '^nfs|cifs|smbfs'
  117. }
  118. # Check that we are running as a regular user
  119. [ "$(id -nu)" = root ] && {
  120. echo "Run this script as a regular user and provide your \"sudo\"" \
  121. "password if requested" >&2
  122. exit 1
  123. }
  124. process_opts "$@"
  125. echo "This script will help you through the process of installing a"
  126. echo "Debian or Ubuntu distribution in a chroot environment. You will"
  127. echo "have to provide your \"sudo\" password when requested."
  128. echo
  129. # Error handler
  130. trap 'exit 1' INT TERM QUIT HUP
  131. trap 'sudo apt-get clean; tput bel; echo; echo Failed' EXIT
  132. # Install any missing applications that this script relies on. If these packages
  133. # are already installed, don't force another "apt-get install". That would
  134. # prevent them from being auto-removed, if they ever become eligible for that.
  135. # And as this script only needs the packages once, there is no good reason to
  136. # introduce a hard dependency on things such as dchroot and debootstrap.
  137. dep=
  138. for i in dchroot debootstrap libwww-perl; do
  139. [ -d /usr/share/doc/"$i" ] || dep="$dep $i"
  140. done
  141. [ -n "$dep" ] && sudo apt-get -y install $dep
  142. sudo apt-get -y install schroot
  143. # Create directory for chroot
  144. sudo mkdir -p /var/lib/chroot
  145. # Find chroot environments that can be installed with debootstrap
  146. targets="$(cd /usr/share/debootstrap/scripts
  147. ls | grep '^[a-z]*$')"
  148. # Ask user to pick one of the available targets
  149. echo "The following targets are available to be installed in a chroot:"
  150. j=1; for i in $targets; do
  151. printf '%4d: %s\n' "$j" "$i"
  152. j=$(($j+1))
  153. done
  154. while :; do
  155. printf "Which target would you like to install: "
  156. read n
  157. [ "$n" -gt 0 -a "$n" -lt "$j" ] >&/dev/null && break
  158. done
  159. j=1; for i in $targets; do
  160. [ "$j" -eq "$n" ] && { distname="$i"; break; }
  161. j=$(($j+1))
  162. done
  163. echo
  164. # On x86-64, ask whether the user wants to install x86-32 or x86-64
  165. archflag=
  166. arch=
  167. if [ "$(uname -m)" = x86_64 ]; then
  168. while :; do
  169. echo "You are running a 64bit kernel. This allows you to install either a"
  170. printf "32bit or a 64bit chroot environment. %s" \
  171. "Which one do you want (32, 64) "
  172. read arch
  173. [ "${arch}" == 32 -o "${arch}" == 64 ] && break
  174. done
  175. [ "${arch}" == 32 ] && archflag="--arch i386" || archflag="--arch amd64"
  176. arch="${arch}bit"
  177. echo
  178. fi
  179. target="${distname}${arch}"
  180. # Don't accidentally overwrite an existing installation
  181. [ -d /var/lib/chroot/"${target}" ] && {
  182. while :; do
  183. echo "This chroot already exists on your machine."
  184. if schroot -l --all-sessions 2>&1 |
  185. sed 's/^session://' |
  186. grep -qs "^${target%bit}-"; then
  187. echo "And it appears to be in active use. Terminate all programs that"
  188. echo "are currently using the chroot environment and then re-run this"
  189. echo "script."
  190. echo "If you still get an error message, you might have stale mounts"
  191. echo "that you forgot to delete. You can always clean up mounts by"
  192. echo "executing \"${target%bit} -c\"."
  193. exit 1
  194. fi
  195. echo "I can abort installation, I can overwrite the existing chroot,"
  196. echo "or I can delete the old one and then exit. What would you like to"
  197. printf "do (a/o/d)? "
  198. read choice
  199. case "${choice}" in
  200. a|A) exit 1;;
  201. o|O) sudo rm -rf "/var/lib/chroot/${target}"; break;;
  202. d|D) sudo rm -rf "/var/lib/chroot/${target}" \
  203. "/usr/local/bin/${target%bit}" \
  204. "/etc/schroot/mount-${target}" \
  205. "/etc/schroot/script-${target}" \
  206. "/etc/schroot/${target}"
  207. sudo sed -ni '/^[[]'"${target%bit}"']$/,${
  208. :1;n;/^[[]/b2;b1;:2;p;n;b2};p' \
  209. "/etc/schroot/schroot.conf"
  210. trap '' INT TERM QUIT HUP
  211. trap '' EXIT
  212. echo "Deleted!"
  213. exit 0;;
  214. esac
  215. done
  216. echo
  217. }
  218. sudo mkdir -p /var/lib/chroot/"${target}"
  219. # Offer to include additional standard repositories for Ubuntu-based chroots.
  220. alt_repos=
  221. grep -qs ubuntu.com /usr/share/debootstrap/scripts/"${distname}" && {
  222. while :; do
  223. echo "Would you like to add ${distname}-updates and ${distname}-security "
  224. printf "to the chroot's sources.list (y/n)? "
  225. read alt_repos
  226. case "${alt_repos}" in
  227. y|Y)
  228. alt_repos="y"
  229. break
  230. ;;
  231. n|N)
  232. break
  233. ;;
  234. esac
  235. done
  236. echo
  237. }
  238. # Check for non-standard file system mount points and ask the user whether
  239. # they should be imported into the chroot environment
  240. # We limit to the first 26 mount points that much some basic heuristics,
  241. # because a) that allows us to enumerate choices with a single character,
  242. # and b) if we find more than 26 mount points, then these are probably
  243. # false-positives and something is very unusual about the system's
  244. # configuration. No need to spam the user with even more information that
  245. # is likely completely irrelevant.
  246. if [ -z "${bind_mounts}" ]; then
  247. mounts="$(awk '$2 != "/" && $2 !~ "^/boot" && $2 !~ "^/home" &&
  248. $2 !~ "^/media" && $2 !~ "^/run" &&
  249. ($3 ~ "ext[2-4]" || $3 == "reiserfs" || $3 == "btrfs" ||
  250. $3 == "xfs" || $3 == "jfs" || $3 == "u?msdos" ||
  251. $3 == "v?fat" || $3 == "hfs" || $3 == "ntfs" ||
  252. $3 ~ "nfs[4-9]?" || $3 == "smbfs" || $3 == "cifs") {
  253. print $2
  254. }' /proc/mounts |
  255. head -n26)"
  256. if [ -n "${mounts}" ]; then
  257. echo "You appear to have non-standard mount points that you"
  258. echo "might want to import into the chroot environment:"
  259. echo
  260. sel=
  261. while :; do
  262. # Print a menu, listing all non-default mounts of local or network
  263. # file systems.
  264. j=1; for m in ${mounts}; do
  265. c="$(printf $(printf '\\%03o' $((64+$j))))"
  266. echo "$sel" | grep -qs $c &&
  267. state="mounted in chroot" || state="$(tput el)"
  268. printf " $c) %-40s${state}\n" "$m"
  269. j=$(($j+1))
  270. done
  271. # Allow user to interactively (de-)select any of the entries
  272. echo
  273. printf "Select mount points that you want to be included or press %s" \
  274. "SPACE to continue"
  275. c="$(getkey | tr a-z A-Z)"
  276. [ "$c" == " " ] && { echo; echo; break; }
  277. if [ -z "$c" ] ||
  278. [ "$c" '<' 'A' -o $(ord "$c") -gt $((64 + $(ord "$j"))) ]; then
  279. # Invalid input, ring the console bell
  280. tput bel
  281. else
  282. # Toggle the selection for the given entry
  283. if echo "$sel" | grep -qs $c; then
  284. sel="$(printf "$sel" | sed "s/$c//")"
  285. else
  286. sel="$sel$c"
  287. fi
  288. fi
  289. # Reposition cursor to the top of the list of entries
  290. tput cuu $(($j + 1))
  291. echo
  292. done
  293. fi
  294. j=1; for m in ${mounts}; do
  295. c="$(chr $(($j + 64)))"
  296. if echo "$sel" | grep -qs $c; then
  297. bind_mounts="${bind_mounts}$m $m none rw,bind 0 0
  298. "
  299. fi
  300. j=$(($j+1))
  301. done
  302. fi
  303. # Remove stale entry from /etc/schroot/schroot.conf. Entries start
  304. # with the target name in square brackets, followed by an arbitrary
  305. # number of lines. The entry stops when either the end of file has
  306. # been reached, or when the beginning of a new target is encountered.
  307. # This means, we cannot easily match for a range of lines in
  308. # "sed". Instead, we actually have to iterate over each line and check
  309. # whether it is the beginning of a new entry.
  310. sudo sed -ni '/^[[]'"${target%bit}"']$/,${:1;n;/^[[]/b2;b1;:2;p;n;b2};p' \
  311. /etc/schroot/schroot.conf
  312. # Download base system. This takes some time
  313. if [ -z "${mirror}" ]; then
  314. grep -qs ubuntu.com /usr/share/debootstrap/scripts/"${distname}" &&
  315. mirror="http://archive.ubuntu.com/ubuntu" ||
  316. mirror="http://ftp.us.debian.org/debian"
  317. fi
  318. sudo ${http_proxy:+http_proxy="${http_proxy}"} debootstrap ${archflag} \
  319. "${distname}" "/var/lib/chroot/${target}" "$mirror"
  320. # Add new entry to /etc/schroot/schroot.conf
  321. grep -qs ubuntu.com /usr/share/debootstrap/scripts/"${distname}" &&
  322. brand="Ubuntu" || brand="Debian"
  323. if [ -z "${chroot_groups}" ]; then
  324. chroot_groups="${admin},$(id -gn)"
  325. fi
  326. if [ -d '/etc/schroot/default' ]; then
  327. new_version=1
  328. fstab="/etc/schroot/${target}/fstab"
  329. else
  330. new_version=0
  331. fstab="/etc/schroot/mount-${target}"
  332. fi
  333. if [ "$new_version" = "1" ]; then
  334. sudo cp -ar /etc/schroot/default /etc/schroot/${target}
  335. sudo sh -c 'cat >>/etc/schroot/schroot.conf' <<EOF
  336. [${target%bit}]
  337. description=${brand} ${distname} ${arch}
  338. type=directory
  339. directory=/var/lib/chroot/${target}
  340. users=root
  341. groups=${chroot_groups}
  342. root-groups=${chroot_groups}
  343. personality=linux$([ "${arch}" != 64bit ] && echo 32)
  344. profile=${target}
  345. EOF
  346. [ -n "${bind_mounts}" -a "${bind_mounts}" != "NONE" ] &&
  347. printf "${bind_mounts}" |
  348. sudo sh -c "cat >>${fstab}"
  349. else
  350. # Older versions of schroot wanted a "priority=" line, whereas recent
  351. # versions deprecate "priority=" and warn if they see it. We don't have
  352. # a good feature test, but scanning for the string "priority=" in the
  353. # existing "schroot.conf" file is a good indication of what to do.
  354. priority=$(grep -qs 'priority=' /etc/schroot/schroot.conf &&
  355. echo 'priority=3' || :)
  356. sudo sh -c 'cat >>/etc/schroot/schroot.conf' <<EOF
  357. [${target%bit}]
  358. description=${brand} ${distname} ${arch}
  359. type=directory
  360. directory=/var/lib/chroot/${target}
  361. users=root
  362. groups=${chroot_groups}
  363. root-groups=${chroot_groups}
  364. personality=linux$([ "${arch}" != 64bit ] && echo 32)
  365. script-config=script-${target}
  366. ${priority}
  367. EOF
  368. # Set up a list of mount points that is specific to this
  369. # chroot environment.
  370. sed '/^FSTAB=/s,"[^"]*","'"${fstab}"'",' \
  371. /etc/schroot/script-defaults |
  372. sudo sh -c 'cat >/etc/schroot/script-'"${target}"
  373. sed '\,^/home[/[:space:]],s/\([,[:space:]]\)bind[[:space:]]/\1rbind /' \
  374. /etc/schroot/mount-defaults |
  375. sudo sh -c "cat > ${fstab}"
  376. fi
  377. # Add the extra mount points that the user told us about
  378. [ -n "${bind_mounts}" -a "${bind_mounts}" != "NONE" ] &&
  379. printf "${bind_mounts}" |
  380. sudo sh -c 'cat >>'"${fstab}"
  381. # If this system has a "/media" mountpoint, import it into the chroot
  382. # environment. Most modern distributions use this mount point to
  383. # automatically mount devices such as CDROMs, USB sticks, etc...
  384. if [ -d /media ] &&
  385. ! grep -qs '^/media' "${fstab}"; then
  386. echo '/media /media none rw,rbind 0 0' |
  387. sudo sh -c 'cat >>'"${fstab}"
  388. fi
  389. # Share /dev/shm, /run and /run/shm.
  390. grep -qs '^/dev/shm' "${fstab}" ||
  391. echo '/dev/shm /dev/shm none rw,bind 0 0' |
  392. sudo sh -c 'cat >>'"${fstab}"
  393. if [ ! -d "/var/lib/chroot/${target}/run" ] &&
  394. ! grep -qs '^/run' "${fstab}"; then
  395. echo '/run /run none rw,bind 0 0' |
  396. sudo sh -c 'cat >>'"${fstab}"
  397. fi
  398. if ! grep -qs '^/run/shm' "${fstab}"; then
  399. { [ -d /run ] && echo '/run/shm /run/shm none rw,bind 0 0' ||
  400. echo '/dev/shm /run/shm none rw,bind 0 0'; } |
  401. sudo sh -c 'cat >>'"${fstab}"
  402. fi
  403. # Set up a special directory that changes contents depending on the target
  404. # that is executing.
  405. d="$(readlink -f "${HOME}/chroot" 2>/dev/null || echo "${HOME}/chroot")"
  406. s="${d}/.${target}"
  407. echo "${s} ${d} none rw,bind 0 0" |
  408. sudo sh -c 'cat >>'"${target}"
  409. mkdir -p "${s}"
  410. # Install a helper script to launch commands in the chroot
  411. sudo sh -c 'cat >/usr/local/bin/'"${target%bit}" <<'EOF'
  412. #!/bin/bash
  413. chroot="${0##*/}"
  414. wrap() {
  415. # Word-wrap the text passed-in on stdin. Optionally, on continuation lines
  416. # insert the same number of spaces as the number of characters in the
  417. # parameter(s) passed to this function.
  418. # If the "fold" program cannot be found, or if the actual width of the
  419. # terminal cannot be determined, this function doesn't attempt to do any
  420. # wrapping.
  421. local f="$(type -P fold)"
  422. [ -z "${f}" ] && { cat; return; }
  423. local c="$(stty -a </dev/tty 2>/dev/null |
  424. sed 's/.*columns[[:space:]]*\([0-9]*\).*/\1/;t;d')"
  425. [ -z "${c}" ] && { cat; return; }
  426. local i="$(echo "$*"|sed 's/./ /g')"
  427. local j="$(printf %s "${i}"|wc -c)"
  428. if [ "${c}" -gt "${j}" ]; then
  429. dd bs=1 count="${j}" 2>/dev/null
  430. "${f}" -sw "$((${c}-${j}))" | sed '2,$s/^/'"${i}"'/'
  431. else
  432. "${f}" -sw "${c}"
  433. fi
  434. }
  435. help() {
  436. echo "Usage ${0##*/} [-h|--help] [-c|--clean] [-C|--clean-all] [-l|--list] [--] args" | wrap "Usage ${0##*/} "
  437. echo " help: print this message" | wrap " "
  438. echo " list: list all known chroot environments" | wrap " "
  439. echo " clean: remove all old chroot sessions for \"${chroot}\"" | wrap " "
  440. echo " clean-all: remove all old chroot sessions for all environments" | wrap " "
  441. exit 0
  442. }
  443. clean() {
  444. local s t rc
  445. rc=0
  446. for s in $(schroot -l --all-sessions); do
  447. if [ -n "$1" ]; then
  448. t="${s#session:}"
  449. [ "${t#${chroot}-}" == "${t}" ] && continue
  450. fi
  451. if ls -l /proc/*/{cwd,fd} 2>/dev/null |
  452. fgrep -qs "/var/lib/schroot/mount/${t}"; then
  453. echo "Session \"${t}\" still has active users, not cleaning up" | wrap
  454. rc=1
  455. continue
  456. fi
  457. sudo schroot -c "${s}" -e || rc=1
  458. done
  459. exit ${rc}
  460. }
  461. list() {
  462. for e in $(schroot -l); do
  463. e="${e#chroot:}"
  464. [ -x "/usr/local/bin/${e}" ] || continue
  465. if schroot -l --all-sessions 2>/dev/null |
  466. sed 's/^session://' |
  467. grep -qs "^${e}-"; then
  468. echo "${e} is currently active"
  469. else
  470. echo "${e}"
  471. fi
  472. done
  473. exit 0
  474. }
  475. while [ "$#" -ne 0 ]; do
  476. case "$1" in
  477. --) shift; break;;
  478. -h|--help) shift; help;;
  479. -l|--list) shift; list;;
  480. -c|--clean) shift; clean "${chroot}";;
  481. -C|--clean-all) shift; clean;;
  482. *) break;;
  483. esac
  484. done
  485. # Start a new chroot session and keep track of the session id. We inject this
  486. # id into all processes that run inside the chroot. Unless they go out of their
  487. # way to clear their environment, we can then later identify our child and
  488. # grand-child processes by scanning their environment.
  489. session="$(schroot -c "${chroot}" -b)"
  490. export CHROOT_SESSION_ID="${session}"
  491. # Set GOMA_TMP_DIR for better handling of goma inside chroot.
  492. export GOMA_TMP_DIR="/tmp/goma_tmp_$CHROOT_SESSION_ID"
  493. mkdir -p "$GOMA_TMP_DIR"
  494. if [ $# -eq 0 ]; then
  495. # Run an interactive shell session
  496. schroot -c "${session}" -r -p
  497. else
  498. # Run a command inside of the chroot environment
  499. p="$1"; shift
  500. schroot -c "${session}" -r -p "$p" -- "$@"
  501. fi
  502. rc=$?
  503. # Compute the inode of the root directory inside of the chroot environment.
  504. i=$(schroot -c "${session}" -r -p ls -- -id /proc/self/root/. |
  505. awk '{ print $1 }') 2>/dev/null
  506. other_pids=
  507. while [ -n "$i" ]; do
  508. # Identify processes by the inode number of their root directory. Then
  509. # remove all processes that we know belong to other sessions. We use
  510. # "sort | uniq -u" to do what amounts to a "set subtraction operation".
  511. pids=$({ ls -id1 /proc/*/root/. 2>/dev/null |
  512. sed -e 's,^[^0-9]*'$i'.*/\([1-9][0-9]*\)/.*$,\1,
  513. t
  514. d';
  515. echo "${other_pids}";
  516. echo "${other_pids}"; } | sort | uniq -u) >/dev/null 2>&1
  517. # Kill all processes that are still left running in the session. This is
  518. # typically an assortment of daemon processes that were started
  519. # automatically. They result in us being unable to tear down the session
  520. # cleanly.
  521. [ -z "${pids}" ] && break
  522. for j in $pids; do
  523. # Unfortunately, the way that schroot sets up sessions has the
  524. # side-effect of being unable to tell one session apart from another.
  525. # This can result in us attempting to kill processes in other sessions.
  526. # We make a best-effort to avoid doing so.
  527. k="$( ( xargs -0 -n1 </proc/$j/environ ) 2>/dev/null |
  528. sed 's/^CHROOT_SESSION_ID=/x/;t1;d;:1;q')"
  529. if [ -n "${k}" -a "${k#x}" != "${session}" ]; then
  530. other_pids="${other_pids}
  531. ${j}"
  532. continue
  533. fi
  534. kill -9 $pids
  535. done
  536. done
  537. # End the chroot session. This should clean up all temporary files. But if we
  538. # earlier failed to terminate all (daemon) processes inside of the session,
  539. # deleting the session could fail. When that happens, the user has to manually
  540. # clean up the stale files by invoking us with "--clean" after having killed
  541. # all running processes.
  542. schroot -c "${session}" -e
  543. # Since no goma processes are running, we can remove goma directory.
  544. rm -rf "$GOMA_TMP_DIR"
  545. exit $rc
  546. EOF
  547. sudo chown root:root /usr/local/bin/"${target%bit}"
  548. sudo chmod 755 /usr/local/bin/"${target%bit}"
  549. # Add the standard Ubuntu update repositories if requested.
  550. [ "${alt_repos}" = "y" -a \
  551. -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] &&
  552. sudo sed -i '/^deb .* [^ -]\+ main$/p
  553. s/^\(deb .* [^ -]\+\) main/\1-security main/
  554. p
  555. t1
  556. d
  557. :1;s/-security main/-updates main/
  558. t
  559. d' "/var/lib/chroot/${target}/etc/apt/sources.list"
  560. # Add a few more repositories to the chroot
  561. [ -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] &&
  562. sudo sed -i 's/ main$/ main restricted universe multiverse/' \
  563. "/var/lib/chroot/${target}/etc/apt/sources.list"
  564. # Add the Ubuntu "partner" repository, if available
  565. if [ -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] &&
  566. HEAD "http://archive.canonical.com/ubuntu/dists/${distname}/partner" \
  567. >&/dev/null; then
  568. sudo sh -c '
  569. echo "deb http://archive.canonical.com/ubuntu" \
  570. "'"${distname}"' partner" \
  571. >>"/var/lib/chroot/'"${target}"'/etc/apt/sources.list"'
  572. fi
  573. # Add source repositories, if the user requested we do so
  574. [ "${add_srcs}" = "y" -a \
  575. -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] &&
  576. sudo sed -i '/^deb[^-]/p
  577. s/^deb\([^-]\)/deb-src\1/' \
  578. "/var/lib/chroot/${target}/etc/apt/sources.list"
  579. # Set apt proxy if host has set http_proxy
  580. if [ -n "${http_proxy}" ]; then
  581. sudo sh -c '
  582. echo "Acquire::http::proxy \"'"${http_proxy}"'\";" \
  583. >>"/var/lib/chroot/'"${target}"'/etc/apt/apt.conf"'
  584. fi
  585. # Update packages
  586. sudo "/usr/local/bin/${target%bit}" /bin/sh -c '
  587. apt-get update; apt-get -y dist-upgrade' || :
  588. # Install a couple of missing packages
  589. for i in debian-keyring ubuntu-keyring locales sudo; do
  590. [ -d "/var/lib/chroot/${target}/usr/share/doc/$i" ] ||
  591. sudo "/usr/local/bin/${target%bit}" apt-get -y install "$i" || :
  592. done
  593. # Configure locales
  594. sudo "/usr/local/bin/${target%bit}" /bin/sh -c '
  595. l='"${LANG:-en_US}"'; l="${l%%.*}"
  596. [ -r /etc/locale.gen ] &&
  597. sed -i "s/^# \($l\)/\1/" /etc/locale.gen
  598. locale-gen $LANG en_US en_US.UTF-8' || :
  599. # Enable multi-arch support, if available
  600. sudo "/usr/local/bin/${target%bit}" dpkg --assert-multi-arch >&/dev/null &&
  601. [ -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] && {
  602. sudo sed -i 's/ / [arch=amd64,i386] /' \
  603. "/var/lib/chroot/${target}/etc/apt/sources.list"
  604. [ -d /var/lib/chroot/${target}/etc/dpkg/dpkg.cfg.d/ ] &&
  605. sudo "/usr/local/bin/${target%bit}" dpkg --add-architecture \
  606. $([ "${arch}" = "32bit" ] && echo amd64 || echo i386) >&/dev/null ||
  607. echo foreign-architecture \
  608. $([ "${arch}" = "32bit" ] && echo amd64 || echo i386) |
  609. sudo sh -c \
  610. "cat >'/var/lib/chroot/${target}/etc/dpkg/dpkg.cfg.d/multiarch'"
  611. }
  612. # Configure "sudo" package
  613. sudo "/usr/local/bin/${target%bit}" /bin/sh -c '
  614. egrep -qs '"'^$(id -nu) '"' /etc/sudoers ||
  615. echo '"'$(id -nu) ALL=(ALL) ALL'"' >>/etc/sudoers'
  616. # Install a few more commonly used packages
  617. sudo "/usr/local/bin/${target%bit}" apt-get -y install \
  618. autoconf automake1.9 dpkg-dev g++-multilib gcc-multilib gdb less libtool \
  619. lsof strace
  620. # If running a 32bit environment on a 64bit machine, install a few binaries
  621. # as 64bit. This is only done automatically if the chroot distro is the same as
  622. # the host, otherwise there might be incompatibilities in build settings or
  623. # runtime dependencies. The user can force it with the '-c' flag.
  624. host_distro=$(grep -s DISTRIB_CODENAME /etc/lsb-release | \
  625. cut -d "=" -f 2)
  626. if [ "${copy_64}" = "y" -o \
  627. "${host_distro}" = "${distname}" -a "${arch}" = 32bit ] && \
  628. file /bin/bash 2>/dev/null | grep -q x86-64; then
  629. readlinepkg=$(sudo "/usr/local/bin/${target%bit}" sh -c \
  630. 'apt-cache search "lib64readline.\$" | sort | tail -n 1 | cut -d " " -f 1')
  631. sudo "/usr/local/bin/${target%bit}" apt-get -y install \
  632. lib64expat1 lib64ncurses5 ${readlinepkg} lib64z1 lib64stdc++6
  633. dep=
  634. for i in binutils gdb; do
  635. [ -d /usr/share/doc/"$i" ] || dep="$dep $i"
  636. done
  637. [ -n "$dep" ] && sudo apt-get -y install $dep
  638. sudo mkdir -p "/var/lib/chroot/${target}/usr/local/lib/amd64"
  639. for i in libbfd libpython; do
  640. lib="$({ ldd /usr/bin/ld; ldd /usr/bin/gdb; } |
  641. grep -s "$i" | awk '{ print $3 }')"
  642. if [ -n "$lib" -a -r "$lib" ]; then
  643. sudo cp "$lib" "/var/lib/chroot/${target}/usr/local/lib/amd64"
  644. fi
  645. done
  646. for lib in libssl libcrypt; do
  647. for path in /usr/lib /usr/lib/x86_64-linux-gnu; do
  648. sudo cp $path/$lib* \
  649. "/var/lib/chroot/${target}/usr/local/lib/amd64/" >&/dev/null || :
  650. done
  651. done
  652. for i in gdb ld; do
  653. sudo cp /usr/bin/$i "/var/lib/chroot/${target}/usr/local/lib/amd64/"
  654. sudo sh -c "cat >'/var/lib/chroot/${target}/usr/local/bin/$i'" <<EOF
  655. #!/bin/sh
  656. exec /lib64/ld-linux-x86-64.so.2 --library-path /usr/local/lib/amd64 \
  657. /usr/local/lib/amd64/$i "\$@"
  658. EOF
  659. sudo chmod 755 "/var/lib/chroot/${target}/usr/local/bin/$i"
  660. done
  661. fi
  662. # If the install-build-deps.sh script can be found, offer to run it now
  663. script="$(dirname $(readlink -f "$0"))/install-build-deps.sh"
  664. if [ -x "${script}" ]; then
  665. while :; do
  666. echo
  667. echo "If you plan on building Chrome inside of the new chroot environment,"
  668. echo "you now have to install the build dependencies. Do you want me to"
  669. printf "start the script that does this for you (y/n)? "
  670. read install_deps
  671. case "${install_deps}" in
  672. y|Y)
  673. echo
  674. # We prefer running the script in-place, but this might not be
  675. # possible, if it lives on a network filesystem that denies
  676. # access to root.
  677. tmp_script=
  678. if ! sudo /usr/local/bin/"${target%bit}" \
  679. sh -c "[ -x '${script}' ]" >&/dev/null; then
  680. tmp_script="/tmp/${script##*/}"
  681. cp "${script}" "${tmp_script}"
  682. fi
  683. # Some distributions automatically start an instance of the system-
  684. # wide dbus daemon, cron daemon or of the logging daemon, when
  685. # installing the Chrome build depencies. This prevents the chroot
  686. # session from being closed. So, we always try to shut down any running
  687. # instance of dbus and rsyslog.
  688. sudo /usr/local/bin/"${target%bit}" sh -c "${script};
  689. rc=$?;
  690. /etc/init.d/cron stop >/dev/null 2>&1 || :;
  691. /etc/init.d/rsyslog stop >/dev/null 2>&1 || :;
  692. /etc/init.d/dbus stop >/dev/null 2>&1 || :;
  693. exit $rc"
  694. rc=$?
  695. [ -n "${tmp_script}" ] && rm -f "${tmp_script}"
  696. [ $rc -ne 0 ] && exit $rc
  697. break
  698. ;;
  699. n|N)
  700. break
  701. ;;
  702. esac
  703. done
  704. echo
  705. fi
  706. # Check whether ~/chroot is on a (slow) network file system and offer to
  707. # relocate it. Also offer relocation, if the user appears to have multiple
  708. # spindles (as indicated by "${bind_mount}" being non-empty).
  709. # We only offer this option, if it doesn't look as if a chroot environment
  710. # is currently active. Otherwise, relocation is unlikely to work and it
  711. # can be difficult for the user to recover from the failed attempt to relocate
  712. # the ~/chroot directory.
  713. # We don't aim to solve this problem for every configuration,
  714. # but try to help with the common cases. For more advanced configuration
  715. # options, the user can always manually adjust things.
  716. mkdir -p "${HOME}/chroot/"
  717. if [ ! -h "${HOME}/chroot" ] &&
  718. ! egrep -qs '^[^[:space:]]*/chroot' /etc/fstab &&
  719. { [ -n "${bind_mounts}" -a "${bind_mounts}" != "NONE" ] ||
  720. is_network_drive "${HOME}/chroot"; } &&
  721. ! egrep -qs '/var/lib/[^/]*chroot/.*/chroot' /proc/mounts; then
  722. echo "${HOME}/chroot is currently located on the same device as your"
  723. echo "home directory."
  724. echo "This might not be what you want. Do you want me to move it somewhere"
  725. echo "else?"
  726. # If the computer has multiple spindles, many users configure all or part of
  727. # the secondary hard disk to be writable by the primary user of this machine.
  728. # Make some reasonable effort to detect this type of configuration and
  729. # then offer a good location for where to put the ~/chroot directory.
  730. suggest=
  731. for i in $(echo "${bind_mounts}"|cut -d ' ' -f 1); do
  732. if [ -d "$i" -a -w "$i" -a \( ! -a "$i/chroot" -o -w "$i/chroot/." \) ] &&
  733. ! is_network_drive "$i"; then
  734. suggest="$i"
  735. else
  736. for j in "$i/"*; do
  737. if [ -d "$j" -a -w "$j" -a \
  738. \( ! -a "$j/chroot" -o -w "$j/chroot/." \) ] &&
  739. ! is_network_drive "$j"; then
  740. suggest="$j"
  741. else
  742. for k in "$j/"*; do
  743. if [ -d "$k" -a -w "$k" -a \
  744. \( ! -a "$k/chroot" -o -w "$k/chroot/." \) ] &&
  745. ! is_network_drive "$k"; then
  746. suggest="$k"
  747. break
  748. fi
  749. done
  750. fi
  751. [ -n "${suggest}" ] && break
  752. done
  753. fi
  754. [ -n "${suggest}" ] && break
  755. done
  756. def_suggest="${HOME}"
  757. if [ -n "${suggest}" ]; then
  758. # For home directories that reside on network drives, make our suggestion
  759. # the default option. For home directories that reside on a local drive,
  760. # require that the user manually enters the new location.
  761. if is_network_drive "${HOME}"; then
  762. def_suggest="${suggest}"
  763. else
  764. echo "A good location would probably be in \"${suggest}\""
  765. fi
  766. fi
  767. while :; do
  768. printf "Physical location [${def_suggest}]: "
  769. read dir
  770. [ -z "${dir}" ] && dir="${def_suggest}"
  771. [ "${dir%%/}" == "${HOME%%/}" ] && break
  772. if ! [ -d "${dir}" -a -w "${dir}" ] ||
  773. [ -a "${dir}/chroot" -a ! -w "${dir}/chroot/." ]; then
  774. echo "Cannot write to ${dir}/chroot. Please try again"
  775. else
  776. mv "${HOME}/chroot" "${dir}/chroot"
  777. ln -s "${dir}/chroot" "${HOME}/chroot"
  778. for i in $(list_all_chroots); do
  779. sudo "$i" mkdir -p "${dir}/chroot"
  780. done
  781. sudo sed -i "s,${HOME}/chroot,${dir}/chroot,g" /etc/schroot/mount-*
  782. break
  783. fi
  784. done
  785. fi
  786. # Clean up package files
  787. sudo schroot -c "${target%bit}" -p -- apt-get clean
  788. sudo apt-get clean
  789. trap '' INT TERM QUIT HUP
  790. trap '' EXIT
  791. # Let the user know what we did
  792. cat <<EOF
  793. Successfully installed ${distname} ${arch}
  794. You can run programs inside of the chroot by invoking the
  795. "/usr/local/bin/${target%bit}" command.
  796. This command can be used with arguments, in order to just run a single
  797. program inside of the chroot environment (e.g. "${target%bit} make chrome")
  798. or without arguments, in order to run an interactive shell session inside
  799. of the chroot environment.
  800. If you need to run things as "root", you can use "sudo" (e.g. try
  801. "sudo ${target%bit} apt-get update").
  802. Your home directory is shared between the host and the chroot. But I
  803. configured "${HOME}/chroot" to be private to the chroot environment.
  804. You can use it for files that need to differ between environments. This
  805. would be a good place to store binaries that you have built from your
  806. source files.
  807. For Chrome, this probably means you want to make your "out" directory a
  808. symbolic link that points somewhere inside of "${HOME}/chroot".
  809. You still need to run "gclient runhooks" whenever you switch from building
  810. outside of the chroot to inside of the chroot. But you will find that you
  811. don't have to repeatedly erase and then completely rebuild all your object
  812. and binary files.
  813. EOF