2 # ==============================================================================
3 # portsreinstall-chroot library script
4 # Overlay onto lib/libfs.sh for portsreinstall-chroot
5 # - File system operations -
6 # Copyright (C) 2018 Mamoru Sakaue, MwGhennndo, All Rights Reserved.
7 # This software is distributed under the 2-Clause BSD License.
8 # ==============================================================================
10 FS_UNMOUNT_RETIAL_MAXNUM=5 # Number of retrial of unmounting
11 FS_UNMOUNT_RETIAL_WAIT=3 # Wait seconds in retrial of unmounting
13 # ============= Check the safety of the base directory of builder chroot environment =============
14 fs_chk_safety_basedir ()
18 [ -n "$basedir" ] || return
20 /|/bin|/boot|/compat|/dev|/entropy|/etc|/home|/host|/lib|/libexec|/net|/proc|/rescue|/root|/sbin|/sys|/tmp|/usr|/var)
24 expr "$basedir" : '^/boot/.*' > /dev/null && return 1
25 expr "$basedir" : '^/compat/.*' > /dev/null && return 1
26 expr "$basedir" : '^/dev/.*' > /dev/null && return 1
27 expr "$basedir" : '^/etc/.*' > /dev/null && return 1
28 expr "$basedir" : '^/lib/.*' > /dev/null && return 1
29 expr "$basedir" : '^/libexec/.*' > /dev/null && return 1
30 expr "$basedir" : '^/proc/.*' > /dev/null && return 1
31 expr "$basedir" : '^/sbin/.*' > /dev/null && return 1
35 # ============= Safeguard of the base directory of builder chroot environment =============
36 fs_safeguard_basedir ()
40 fs_chk_safety_basedir "$basedir" && return
41 message_echo "ERROR: The base directory [$opt_basedir] is not safe." >&2
45 # ============= Save the system base observed currently =============
46 fs_save_current_systembase ()
50 echo "$systembase" > ${TMPDIR}/fs_save_current_systembase
53 # ============= Save the system base observed at the time of mounting =============
54 fs_save_mounttime_systembase ()
58 echo "$systembase" > ${DBDIR}/fs_systembase
61 # ============= Get the system base observed currently =============
62 fs_get_current_systembase ()
64 cat "${TMPDIR}/fs_save_current_systembase" 2> /dev/null
67 # ============= Get the system base observed at the time of mounting =============
68 # Non-zero return means no file system was attempted to mount
69 fs_get_mounttime_systembase ()
71 cat "${DBDIR}/fs_systembase" 2> /dev/null
74 # ============= Get the system base in the scope of accessing =============
75 fs_system_base_in_mp_access ()
77 fs_get_current_systembase
80 # ============= Get the system base in the scope of creating mount points =============
81 fs_system_base_in_mp_creation ()
83 fs_get_current_systembase
86 # ============= Get the system base in the scope of referring to mount points =============
87 fs_system_base_in_mp_reference ()
89 str_regularize_df_path "`fs_get_current_systembase`/`fs_get_system_basedir`"
92 # ============= Get the system base in the scope of targets =============
93 # Non-zero return means no file system was attempted to mount
94 fs_system_base_in_target ()
96 fs_get_mounttime_systembase
99 # ============= Build the file systems for the builder chroot environment =============
102 [ -e "${DBDIR}/mount_manifest" ] && return
103 message_echo "Building the file systems for builder chroot environment (if not yet)."
104 fs_safeguard_basedir "$opt_basedir"
106 mkdir -p "$opt_basedir"
107 # Prescan the f file system of the original environment
108 cp /dev/null "${TMPDIR}/fs_build_chroot:directories"
111 echo bin compat etc lib libexec root sbin sys usr var | tr ' ' '\n'
112 echo "$opt_extra_dirs" | tr "$opt_extra_dirs_delim" '\n'
113 } | grep -v '^[[:space:]]*$' | sort -u > ${TMPDIR}/fs_build_chroot:sys_dirs
114 sysdirs_ptn="^/*(`str_escape_regexp_filter < ${TMPDIR}/fs_build_chroot:sys_dirs | tr '\n' '|' | sed 's/\|$//'`)/+"
117 [ -e "/$directory" ] || continue
118 if [ -L "/$directory" ]
120 src_mountpoint_real=`realpath "/$directory"`
121 printf '%s\t%s\n' link "$directory" >> ${TMPDIR}/fs_build_chroot:directories
122 if ! echo "$src_mountpoint_real/" | grep -qE "$sysdirs_ptn"
124 printf '%s\t%s\n' real "$src_mountpoint_real" >> ${TMPDIR}/fs_build_chroot:directories
125 tmpdir_descendant=${TMPDIR}/fs_build_chroot:descendant/$src_mountpoint_real
126 mkdir -p "$tmpdir_descendant"
127 fs_get_descendant_mount_info "/$src_mountpoint_real" > $tmpdir_descendant/list
129 elif [ -d "/$directory" ]
131 printf '%s\t%s\n' real $directory >> ${TMPDIR}/fs_build_chroot:directories
132 tmpdir_descendant=${TMPDIR}/fs_build_chroot:descendant/$directory
133 mkdir -p "$tmpdir_descendant"
134 fs_get_descendant_mount_info "/$directory" > $tmpdir_descendant/list
136 done < ${TMPDIR}/fs_build_chroot:sys_dirs
138 # Prepare the grand base of the chroot environment
141 for directory in builder mask store
143 [ -d $directory ] || mkdir $directory
146 # Directories to share with the host environment by nullfs
147 if [ "x$opt_share_port_pkgs_dirs" = xyes ]
150 echo "$PORTSNAP_WORKDIR"
151 echo "$PKGNG_PKG_CACHEDIR"
152 fi | str_regularize_df_path_filter | grep -v '^[[:space:]]*$' | sort -u > ${DBDIR}/shared_dirs.lst
153 str_escape_regexp_filter < ${DBDIR}/shared_dirs.lst | sed 's|^|^|;s|$|\/|' > ${TMPDIR}/fs_build_chroot:shared_dirs.regexp.tmp
154 paste "${DBDIR}/shared_dirs.lst" "${TMPDIR}/fs_build_chroot:shared_dirs.regexp.tmp" > ${TMPDIR}/fs_build_chroot:shared_dirs.regexp
155 cp /dev/null "${TMPDIR}/fs_build_chroot:shared_dirs:added"
156 # Build target directories and the manifest for mounting
157 cp /dev/null "${DBDIR}/mount_manifest.tmp"
159 cd "/$opt_basedir"/builder
162 type=`echo "$srcline" | cut -f 1`
163 directory=`echo "$srcline" | cut -f 2`
166 [ -e "$directory" -o -L "$directory" ] || cp -RpP "/$directory" .
169 mkdir -p "./$directory"
170 masktarget=/$opt_basedir/mask/$directory
171 mkdir -p "$masktarget"
172 printf '%s\t%s\t%s\t%s\n' nullfs "/$directory" "$directory" ro >> ${DBDIR}/mount_manifest.tmp
173 printf '%s\t%s\t%s\t%s\n' unionfs "$masktarget" "$directory" rw,noatime >> ${DBDIR}/mount_manifest.tmp
176 fs=`echo "$srcline" | cut -f 1`
177 mp=`echo "$srcline" | cut -f 2 | str_regularize_df_path_filter`
178 relative=`echo "$srcline" | cut -f 3 | str_regularize_df_path_filter`
179 fullpath=`str_regularize_df_path "/$directory/$relative"`
180 rm -f "${TMPDIR}/fs_build_chroot:shared_dirs:is_under"
181 rm -f "${TMPDIR}/fs_build_chroot:shared_dirs:is_itself"
182 while read -r shared_path shared_path_regexp
184 echo "$fullpath/" | grep -qE "$shared_path_regexp" || continue
185 echo "$shared_path"$'\n'"$fullpath" | while read mpath
187 if ! grep -qFx "$mpath" "${TMPDIR}/fs_build_chroot:shared_dirs:added"
189 echo "$mpath" >> ${TMPDIR}/fs_build_chroot:shared_dirs:added
191 mp_share=`realpath "/$mpath"`
192 printf '%s\t%s\t%s\t%s\n' nullfs "$mp_share" "$mpath" rw >> ${DBDIR}/mount_manifest.tmp
195 touch "${TMPDIR}/fs_build_chroot:shared_dirs:is_under"
196 done < ${TMPDIR}/fs_build_chroot:shared_dirs.regexp
197 [ -e "${TMPDIR}/fs_build_chroot:shared_dirs:is_under" ] && continue
200 masktarget=`str_regularize_df_path "/$opt_basedir/mask/$fullpath"`
201 mkdir -p "$masktarget"
202 printf '%s\t%s\t%s\t%s\n' nullfs "$mp" "$fullpath" ro >> ${DBDIR}/mount_manifest.tmp
203 printf '%s\t%s\t%s\t%s\n' unionfs "$masktarget" "$fullpath" rw,noatime >> ${DBDIR}/mount_manifest.tmp
206 printf '%s\t%s\t%s\t%s\n' devfs devfs "$fullpath" rw >> ${DBDIR}/mount_manifest.tmp
209 printf '%s\t%s\t%s\t%s\n' fdescfs fdesc "$fullpath" rw >> ${DBDIR}/mount_manifest.tmp
212 printf '%s\t%s\t%s\t%s\n' procfs proc "$fullpath" rw >> ${DBDIR}/mount_manifest.tmp
215 printf '%s\t%s\t%s\t%s\n' linprocfs linproc "$fullpath" rw >> ${DBDIR}/mount_manifest.tmp
218 printf '%s\t%s\t%s\t%s\n' tmpfs tmpfs "$fullpath" rw,mode=1777 >> ${DBDIR}/mount_manifest.tmp
221 done < ${TMPDIR}/fs_build_chroot:descendant/$directory/list
224 done < ${TMPDIR}/fs_build_chroot:directories
225 grep -Ev -f "${TMPDIR}/fs_build_chroot:shared_dirs:added" "${DBDIR}/shared_dirs.lst" | while read shared_dir
227 mkdir -p "$shared_dir"
228 mp_share=`realpath "$shared_dir"`
229 printf '%s\t%s\t%s\t%s\n' nullfs "$mp_share" "/$shared_dir" rw >> ${DBDIR}/mount_manifest.tmp
231 for directory in dev proc tmp
233 [ -e $directory ] || mkdir $directory
235 printf '%s\t%s\t%s\t%s\n' devfs devfs dev rw >> ${DBDIR}/mount_manifest.tmp
236 printf '%s\t%s\t%s\t%s\n' fdescfs fdesc dev/fd rw >> ${DBDIR}/mount_manifest.tmp
237 printf '%s\t%s\t%s\t%s\n' procfs proc proc rw >> ${DBDIR}/mount_manifest.tmp
238 printf '%s\t%s\t%s\t%s\n' tmpfs tmpfs tmp rw,mode=1777 >> ${DBDIR}/mount_manifest.tmp
239 mkdir -p ."${PROGRAM}"
240 cd "/$opt_basedir/mask"
241 if [ ! -e root/.cshrc ]
243 tmp_cshrc=${TMPDIR}/fs_build_chroot:.cshrc
244 [ -d root ] || mkdir root
245 if [ -e /root/.cshrc ]
247 cp -p /root/.cshrc "$tmp_cshrc"
248 cp -p /root/.cshrc "root/.cshrc.bak-${APPNAME}"
249 elif [ -e /usr/share/skel/dot.cshrc ]
251 cp -p /usr/share/skel/dot.cshrc "$tmp_cshrc"
252 touch "root/.cshrc.bak-${APPNAME}"
254 cp /dev/null "$tmp_cshrc"
257 echo 'set prompt="%N@[builder]%m:%~ %# "' >> $tmp_cshrc
258 mv "$tmp_cshrc" root/.cshrc
260 printf '%s\t%s\t%s\t%s\n' nullfs "/$opt_basedir"/store ".${PROGRAM}" rw >> ${DBDIR}/mount_manifest.tmp
262 mv "${DBDIR}/mount_manifest.tmp" "${DBDIR}/mount_manifest"
265 # ============= Check whether the file systems for the builder chroot environment are all mounted =============
268 local systembase_target systembase_mp tmp_remains
269 [ -e "${DBDIR}/mount_manifest" ] || return
270 systembase_target=`fs_system_base_in_target` || return
271 systembase_mp=`fs_system_base_in_mp_reference`
272 tmp_remains=${TMPDIR}/fs_chk_mount:remains
273 rm -rf "$tmp_remains"
276 type=`echo "$srcline" | cut -f 1`
277 target=`echo "$srcline" | cut -f 2`
278 [ "x$type" = xnullfs -o "x$type" = xunionfs ] && target=`str_regularize_df_path "$systembase_target/$target"`
279 directory=`echo "$srcline" | cut -f 3`
280 opt=`echo "$srcline" | cut -f 4`
281 mp=`str_regularize_df_path "$systembase_mp/$opt_basedir/builder/$directory"`
282 if ! fs_chk_mounted "$type" "$target" "$mp"
284 printf '%s\t%s\t%s\n' "$type" "$target" "$mp" >> $tmp_remains
286 done < ${DBDIR}/mount_manifest
287 ! cat "$tmp_remains" 2> /dev/null
290 # ============= Terminate when the file systems for the builder chroot environment cannot be mounted =============
291 fs_terminate_if_mount_unavailable ()
293 local systembase systembase_saved
294 systembase=`fs_get_current_systembase`
295 fs_chk_mount > /dev/null && return
296 if systembase_saved=`fs_get_mounttime_systembase`
298 if [ "x$systembase" = "x$systembase_saved" ]
300 fs_chk_mount_privilege && return
301 elif [ -n "$systembase" ]
303 temp_terminate_process ()
305 message_echo "ERROR: Cannot mount because the current file systems are being mounted from inside the virtual (chroot or jail) environment." >&2
306 message_echo "INFO: Instead of this command, mount from inside the virtual (chroot or jail) environment."
308 [ $TEMP_IN_TRAP = no ] || temp_terminate_process
311 elif fs_chk_mount_privilege
313 fs_save_mounttime_systembase "`fs_get_current_systembase`"
316 temp_terminate_process ()
319 [ $opt_batch_mode = yes ] && return
321 message_echo "INFO: Terminated for mounting file systems because this utility was executed at a virtual (chroot or jail) environment."
322 message_echo "Execute"
323 basedir=`fs_get_system_basedir`
326 message_echo " $basedir${SHAREDIR}/bin/portsreinstall-chroot-mount"
327 message_echo "at the grand host environment."
329 message_echo " \$BASEDIR${SHAREDIR}/bin/portsreinstall-chroot-mount"
330 message_echo "at the grand host environment, where \$BASEDIR denotes the base directory of this virtual environment."
332 message_echo "After its successful execution, rerun"
333 if [ -n "$COMMAND_RESTART" ]
335 message_echo " ${APPNAME} $COMMAND_RESTART"
337 message_echo " ${APPNAME}"
340 [ $TEMP_IN_TRAP = no ] || temp_terminate_process
344 # ============= Generate a custom fstab file for the builder chroot environment =============
347 local stage systembase_target systembase_mp
349 systembase_target=`fs_system_base_in_target`
352 systembase_mp=`fs_system_base_in_mp_creation`
355 systembase_mp=`fs_system_base_in_mp_reference`
358 fs_safeguard_basedir "$opt_basedir"
361 type=`echo "$srcline" | cut -f 1`
362 target=`echo "$srcline" | cut -f 2`
363 directory=`echo "$srcline" | cut -f 3`
364 opt=`echo "$srcline" | cut -f 4`
365 [ "x$type" = xnullfs -o "x$type" = xunionfs ] && target=$systembase_target/$target
366 target=`str_regularize_df_path "$target"`
367 mp=`str_regularize_df_path "$systembase_mp/$opt_basedir/builder/$directory"`
368 opt=`echo "$opt" | sed 's/[[:space:]]/\\040/g'`
369 echo "$target $mp $type $opt 0 0"
370 done < ${DBDIR}/mount_manifest > ${DBDIR}/fstab
373 # ============= Mount the file systems for the builder chroot environment if not yet =============
377 if fs_chk_mount > /dev/null
379 message_echo "The builder chroot environment is already mounted."
383 fs_terminate_if_mount_unavailable
384 message_section_title "Mounting the file systems for builder chroot environment."
387 if ! mount -F "${DBDIR}/fstab" -a || ! remining=`fs_chk_mount`
389 cp "${DBDIR}/fstab" "${DBDIR}/fstab-mount-err"
390 message_echo "ERROR: Failed to mount the file systems. The followings remain unmounted:" >&2
391 if [ $opt_batch_mode = no ]
393 [ -n "$remining" ] || remining=`fs_chk_mount` || :
398 message_echo "Mounting done."
402 # ============= Check whether the file systems for the builder chroot environment are all unmounted or destroyed =============
405 local systembase_target systembase_mp tmp_remains
406 [ -e "${DBDIR}/mount_manifest" ] || return 0
407 systembase_target=`fs_system_base_in_target` || return 0
408 systembase_mp=`fs_system_base_in_mp_reference`
409 tmp_remains=${TMPDIR}/fs_chk_unmount:remains
410 rm -rf "$tmp_remains"
411 tail -r "${DBDIR}/mount_manifest" | while read srcline
413 type=`echo "$srcline" | cut -f 1`
414 target=`echo "$srcline" | cut -f 2`
415 [ "x$type" = xnullfs -o "x$type" = xunionfs ] && target=$systembase_target/$target
416 directory=`echo "$srcline" | cut -f 3`
417 opt=`echo "$srcline" | cut -f 4`
418 mp=$systembase_mp/$opt_basedir/builder/$directory
419 fs_chk_mounted "$type" "$target" "$mp" || continue
420 str_regularize_df_path "$mp" >> $tmp_remains
422 ! cat "$tmp_remains" 2> /dev/null
425 # ============= Terminate when the file systems for the builder chroot environment cannot be unmounted =============
426 fs_terminate_if_unmount_unavailable ()
428 local systembase systembase_saved
429 systembase=`fs_get_current_systembase`
430 systembase_saved=`fs_get_mounttime_systembase` || return 0
431 fs_chk_unmount > /dev/null && return
432 if [ "x$systembase" = "x$systembase_saved" ]
434 fs_chk_mount_privilege && return
435 elif [ -n "$systembase" ]
437 temp_terminate_process ()
439 message_echo "ERROR: Cannot unmount because the current file systems were mounted from inside the virtual (chroot or jail) environment." >&2
440 message_echo "INFO: Instead of this command, unmount from inside the virtual (chroot or jail) environment."
442 [ $TEMP_IN_TRAP = no ] || temp_terminate_process
445 temp_terminate_process ()
449 [ $opt_batch_mode = yes ] && return
451 message_echo "INFO: Terminated for unmounting file systems because this utility was executed at a virtual (chroot or jail) environment."
452 message_echo "Execute"
453 basedir=`fs_get_system_basedir`
456 message_echo " $basedir${SHAREDIR}/bin/portsreinstall-chroot-mount unmount"
457 message_echo "at the grand host environment."
459 message_echo " \$BASEDIR${SHAREDIR}/bin/portsreinstall-chroot-mount unmount"
460 message_echo "at the grand host environment, where \$BASEDIR denotes the base directory of this virtual environment."
462 message_echo "After its successful execution, rerun"
463 if [ -n "$COMMAND_RESTART" ]
465 message_echo " ${APPNAME} $COMMAND_RESTART"
467 message_echo " ${APPNAME}"
470 [ $TEMP_IN_TRAP = no ] || temp_terminate_process
474 # ============= Unmount file systems for the chroot environment =============
477 local systembase_access systembase_saved remining
478 systembase_access=`fs_system_base_in_mp_access`
479 ! fs_get_mounttime_systembase > /dev/null && return
480 fs_terminate_if_unmount_unavailable
481 [ ! -d "$systembase_access/$opt_basedir"/builder ] && return
482 [ ! -e "${DBDIR}/mount_manifest" ] && return
483 if fs_chk_unmount > /dev/null
485 if [ $TEMP_IN_TRAP = no ]
487 message_echo "The builder chroot environment is already unmounted."
492 if [ $TEMP_IN_TRAP = no ]
494 message_section_title "Unmounting the file systems for builder chroot."
497 message_echo "Unmounting the file systems for builder chroot."
500 umount -F "${DBDIR}/fstab" -af 2> /dev/null || :
501 itrial=${FS_UNMOUNT_RETIAL_MAXNUM}
502 while [ $itrial -gt 0 ]
504 remining=`fs_chk_unmount` && break
505 echo -n "$remining" | str_regularize_df_path_filter | while read remining_mp
507 umount -f "$remining_mp" || :
509 message_echo "(Retrying to unmount the file systems...)" >&2
510 sleep ${FS_UNMOUNT_RETIAL_WAIT}
511 umount -F "${DBDIR}/fstab" -af 2> /dev/null || :
512 itrial=$(($itrial-1))
516 message_echo "WARNING: Failed to unmount the file systems. Some of them remain mounted." >&2
519 rm -f "${DBDIR}/fs_systembase"
520 message_echo "Unmounting done."
524 # ============= Destroy the chroot environment =============
527 fs_chk_safety_basedir "$opt_basedir" || return 0
528 [ ! -d "$opt_basedir" ] && return
530 chflags -R noschg "$opt_basedir"
531 rm -rf "$opt_basedir"