2 # ==============================================================================
3 # portsreinstall-chroot library script
4 # Overlay onto lib/libfs.sh for portsreinstall-chroot
5 # - File system operations -
6 # Copyright (C) 2018-2022 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 ()
65 systembase=`cat "${TMPDIR}/fs_save_current_systembase" 2> /dev/null`
66 [ -z "$systembase" ] || realpath "$systembase"
69 # ============= Get the system base observed at the time of mounting =============
70 # Non-zero return means no file system was attempted to mount
71 fs_get_mounttime_systembase ()
74 systembase=`cat "${DBDIR}/fs_systembase" 2> /dev/null`
75 [ -z "$systembase" ] || realpath "$systembase"
78 # ============= Get the system base in the scope of accessing =============
79 fs_system_base_in_mp_access ()
81 fs_get_current_systembase
84 # ============= Get the system base in the scope of creating mount points =============
85 fs_system_base_in_mp_creation ()
87 fs_get_current_systembase
90 # ============= Get the system base in the scope of referring to mount points =============
91 fs_system_base_in_mp_reference ()
93 str_regularize_df_path "`fs_get_current_systembase`/`fs_get_system_basedir`"
96 # ============= Get the system base in the scope of targets =============
97 # Non-zero return means no file system was attempted to mount
98 fs_system_base_in_target ()
100 fs_get_mounttime_systembase
103 # ============= Build the file systems for the builder chroot environment =============
106 [ -e "${DBDIR}/mount_manifest" ] && return
107 message_echo "Building the file systems for builder chroot environment (if not yet)."
108 fs_safeguard_basedir "$opt_basedir"
110 mkdir -p "$opt_basedir"
111 # Prescan the file system of the original environment
112 cp /dev/null "${TMPDIR}/fs_build_chroot:directories"
114 real_basedir=`realpath "$opt_basedir"`
116 # echo bin compat etc lib libexec root sbin sys usr | tr ' ' '\n'
117 echo bin compat etc lib libexec root sbin sys usr var | tr ' ' '\n'
118 echo "$opt_extra_dirs" | tr "$opt_extra_dirs_delim" '\n'
119 } | env LANG=C grep -v '^[[:space:]]*$' | sort -u | while read node
121 [ -e "/$node" ] || continue
122 src_mountpoint_real=`realpath "/$node"`
123 ptn_src_mountpoint_real=`str_escape_regexp "$src_mountpoint_real/"`
124 if echo "$real_basedir" | grep -Eq "^$ptn_src_mountpoint_real"
128 rm -f "${TMPDIR}/fs_build_chroot:hit_exact"
129 while [ ! -e "${TMPDIR}/fs_build_chroot:hit_exact" ]
131 rm -f "${TMPDIR}/fs_build_chroot:hit_subnode"
133 ls -a | while read subnode
135 if [ "$subnode" = . -o "$subnode" = .. ]
138 elif [ -L "$subnode" -o -f "$subnode" ]
140 echo "$node_cur/$subnode"
141 elif [ -d "$subnode" ]
143 node_cur_tmp_real=`realpath "/$node_cur/$subnode"`
144 ptn_node_cur_tmp_real=`str_escape_regexp "$node_cur_tmp_real/"`
145 if [ "$real_basedir" = "$node_cur_tmp_real" ]
147 touch "${TMPDIR}/fs_build_chroot:hit_exact"
148 elif echo "$real_basedir" | grep -Eq "^$ptn_node_cur_tmp_real"
150 echo "$subnode" > ${TMPDIR}/fs_build_chroot:hit_subnode
152 echo "$node_cur/$subnode"
156 [ -e "${TMPDIR}/fs_build_chroot:hit_subnode" ] || break
157 node_cur=$node_cur/`cat "${TMPDIR}/fs_build_chroot:hit_subnode"`
163 done > ${TMPDIR}/fs_build_chroot:sys_nodes
164 sysdirs_ptn="^/*(`str_escape_regexp_filter < ${TMPDIR}/fs_build_chroot:sys_nodes | tr '\n' '|' | sed 's/\|$//'`)/+"
167 [ -e "/$node" ] || continue
168 if [ -L "/$node" -a -d "/$node" ]
170 src_mountpoint_real=`realpath "/$node"`
171 printf '%s\t%s\n' link "$node" >> ${TMPDIR}/fs_build_chroot:directories
172 if ! echo "$src_mountpoint_real/" | env LANG=C grep -qE "$sysdirs_ptn"
174 printf '%s\t%s\n' real "$src_mountpoint_real" >> ${TMPDIR}/fs_build_chroot:directories
175 tmpdir_descendant=${TMPDIR}/fs_build_chroot:descendant/$src_mountpoint_real
176 mkdir -p "$tmpdir_descendant"
177 fs_get_descendant_mount_info "/$src_mountpoint_real" > $tmpdir_descendant/list
181 printf '%s\t%s\n' real $node >> ${TMPDIR}/fs_build_chroot:directories
182 tmpdir_descendant=${TMPDIR}/fs_build_chroot:descendant/$node
183 mkdir -p "$tmpdir_descendant"
184 fs_get_descendant_mount_info "/$node" > $tmpdir_descendant/list
185 elif [ -L "/$node" -o -f "/$node" ]
187 cp -p "/$node" "$node"
189 done < ${TMPDIR}/fs_build_chroot:sys_nodes
191 # Prepare the grand base of the chroot environment
194 for directory in builder mask store
196 [ -d $directory ] || mkdir $directory
199 # Directories to share with the host environment by nullfs
200 if [ "x$opt_share_port_pkgs_dirs" = xyes ]
203 echo "$PORTSNAP_WORKDIR"
204 echo "$PKGNG_PKG_CACHEDIR"
205 fi | str_regularize_df_path_filter | env LANG=C grep -v '^[[:space:]]*$' | sort -u > ${DBDIR}/shared_dirs.lst
206 str_escape_regexp_filter < ${DBDIR}/shared_dirs.lst | sed 's|^|^|;s|$|\/|' > ${TMPDIR}/fs_build_chroot:shared_dirs.regexp.tmp
207 paste "${DBDIR}/shared_dirs.lst" "${TMPDIR}/fs_build_chroot:shared_dirs.regexp.tmp" > ${TMPDIR}/fs_build_chroot:shared_dirs.regexp
208 cp /dev/null "${TMPDIR}/fs_build_chroot:shared_dirs:added"
209 # Build target directories and the manifest for mounting
210 mkdir -p "$opt_basedir/mask"
211 cp /dev/null "${DBDIR}/mount_manifest.tmp"
213 real_basedir=`realpath "$opt_basedir"`
214 cd "$real_basedir"/builder
217 type=`echo "$srcline" | cut -f 1`
218 directory=`echo "$srcline" | cut -f 2`
221 [ -e "$directory" -o -L "$directory" ] || cp -RpP "/$directory" .
224 mkdir -p "./$directory"
225 masktarget=$real_basedir/mask/$directory
226 mkdir -p "$masktarget"
227 printf '%s\t%s\t%s\t%s\n' nullfs "/$directory" "$directory" ro >> ${DBDIR}/mount_manifest.tmp
228 printf '%s\t%s\t%s\t%s\n' unionfs "$masktarget" "$directory" rw,noatime >> ${DBDIR}/mount_manifest.tmp
231 fs=`echo "$srcline" | cut -f 1`
232 mp=`echo "$srcline" | cut -f 2 | str_regularize_df_path_filter`
233 relative=`echo "$srcline" | cut -f 3 | str_regularize_df_path_filter`
234 fullpath=`str_regularize_df_path "/$directory/$relative"`
235 rm -f "${TMPDIR}/fs_build_chroot:shared_dirs:is_under"
236 rm -f "${TMPDIR}/fs_build_chroot:shared_dirs:is_itself"
237 while read -r shared_path shared_path_regexp
239 echo "$fullpath/" | env LANG=C grep -qE "$shared_path_regexp" || continue
240 echo "$shared_path"$'\n'"$fullpath" | while read mpath
242 if ! env LANG=C grep -qFx "$mpath" "${TMPDIR}/fs_build_chroot:shared_dirs:added"
244 echo "$mpath" >> ${TMPDIR}/fs_build_chroot:shared_dirs:added
246 mp_share=`realpath "/$mpath"`
247 printf '%s\t%s\t%s\t%s\n' nullfs "$mp_share" "$mpath" rw >> ${DBDIR}/mount_manifest.tmp
250 touch "${TMPDIR}/fs_build_chroot:shared_dirs:is_under"
251 done < ${TMPDIR}/fs_build_chroot:shared_dirs.regexp
252 [ -e "${TMPDIR}/fs_build_chroot:shared_dirs:is_under" ] && continue
255 masktarget=`str_regularize_df_path "$real_basedir/mask/$fullpath"`
256 mkdir -p "$masktarget"
257 printf '%s\t%s\t%s\t%s\n' nullfs "$mp" "$fullpath" ro >> ${DBDIR}/mount_manifest.tmp
258 printf '%s\t%s\t%s\t%s\n' unionfs "$masktarget" "$fullpath" rw,noatime >> ${DBDIR}/mount_manifest.tmp
261 printf '%s\t%s\t%s\t%s\n' devfs devfs "$fullpath" rw >> ${DBDIR}/mount_manifest.tmp
264 printf '%s\t%s\t%s\t%s\n' fdescfs fdesc "$fullpath" rw >> ${DBDIR}/mount_manifest.tmp
267 printf '%s\t%s\t%s\t%s\n' procfs proc "$fullpath" rw >> ${DBDIR}/mount_manifest.tmp
270 printf '%s\t%s\t%s\t%s\n' linprocfs linproc "$fullpath" rw >> ${DBDIR}/mount_manifest.tmp
273 printf '%s\t%s\t%s\t%s\n' tmpfs tmpfs "$fullpath" rw,mode=1777 >> ${DBDIR}/mount_manifest.tmp
276 done < ${TMPDIR}/fs_build_chroot:descendant/$directory/list
279 done < ${TMPDIR}/fs_build_chroot:directories
280 env LANG=C grep -Ev -f "${TMPDIR}/fs_build_chroot:shared_dirs:added" "${DBDIR}/shared_dirs.lst" | while read shared_dir
282 mkdir -p "$shared_dir"
283 mp_share=`realpath "$shared_dir"`
284 printf '%s\t%s\t%s\t%s\n' nullfs "$mp_share" "/$shared_dir" rw >> ${DBDIR}/mount_manifest.tmp
286 for directory in dev proc tmp
288 [ -e $directory ] || mkdir $directory
290 printf '%s\t%s\t%s\t%s\n' devfs devfs dev rw >> ${DBDIR}/mount_manifest.tmp
291 printf '%s\t%s\t%s\t%s\n' fdescfs fdesc dev/fd rw >> ${DBDIR}/mount_manifest.tmp
292 printf '%s\t%s\t%s\t%s\n' procfs proc proc rw >> ${DBDIR}/mount_manifest.tmp
293 printf '%s\t%s\t%s\t%s\n' tmpfs tmpfs tmp rw,mode=1777 >> ${DBDIR}/mount_manifest.tmp
294 mkdir -p ."${PROGRAM}"
295 cd "$real_basedir/mask"
296 if [ ! -e root/.cshrc ]
298 tmp_cshrc=${TMPDIR}/fs_build_chroot:.cshrc
299 [ -d root ] || mkdir root
300 if [ -e /root/.cshrc ]
302 cp -p /root/.cshrc "$tmp_cshrc"
303 cp -p /root/.cshrc "root/.cshrc.bak-${APPNAME}"
304 elif [ -e /usr/share/skel/dot.cshrc ]
306 cp -p /usr/share/skel/dot.cshrc "$tmp_cshrc"
307 touch "root/.cshrc.bak-${APPNAME}"
309 cp /dev/null "$tmp_cshrc"
312 echo 'set prompt="%N@[builder]%m:%~ %# "' >> $tmp_cshrc
313 mv "$tmp_cshrc" root/.cshrc
315 printf '%s\t%s\t%s\t%s\n' nullfs "$real_basedir"/store ".${PROGRAM}" rw >> ${DBDIR}/mount_manifest.tmp
317 mv "${DBDIR}/mount_manifest.tmp" "${DBDIR}/mount_manifest"
320 # ============= Check whether the file systems for the builder chroot environment are all mounted =============
323 local systembase_target systembase_mp tmp_remains
324 [ -e "${DBDIR}/mount_manifest" ] || return
325 systembase_target=`fs_system_base_in_target` || return
326 systembase_mp=`fs_system_base_in_mp_reference`
327 tmp_remains=${TMPDIR}/fs_chk_mount:remains
328 rm -rf "$tmp_remains"
331 type=`echo "$srcline" | cut -f 1`
332 target=`echo "$srcline" | cut -f 2`
333 [ "x$type" = xnullfs -o "x$type" = xunionfs ] && target=`str_regularize_df_path "$systembase_target/$target"`
334 directory=`echo "$srcline" | cut -f 3`
335 opt=`echo "$srcline" | cut -f 4`
336 mp=`str_regularize_df_path "$systembase_mp/$opt_basedir/builder/$directory"`
337 expr "$target" : \\/ && target=`realpath "$target"`
338 if ! real_mp=`realpath "$mp" 2> /dev/null` || ! fs_chk_mounted "$type" "$target" "$real_mp"
340 printf '%s\t%s\t%s\n' "$type" "$target" "$mp" >> $tmp_remains
342 done < ${DBDIR}/mount_manifest
343 ! cat "$tmp_remains" 2> /dev/null
346 # ============= Terminate when the file systems for the builder chroot environment cannot be mounted: For mounting at the grand host =============
347 fs_terminate_if_mount_unavailable__mount_at_host () { :; }
349 # ============= Terminate when the file systems for the builder chroot environment cannot be mounted =============
350 fs_terminate_if_mount_unavailable ()
352 local systembase systembase_saved
353 systembase=`fs_get_current_systembase`
354 fs_chk_mount > /dev/null && return
355 if systembase_saved=`fs_get_mounttime_systembase`
357 if [ "x$systembase" = "x$systembase_saved" ]
359 fs_chk_mount_privilege && return
360 elif [ -n "$systembase" ]
362 fs_terminate_if_mount_unavailable__mount_at_host
364 elif fs_chk_mount_privilege
366 fs_save_mounttime_systembase "`fs_get_current_systembase`"
369 temp_terminate_process ()
372 [ $opt_batch_mode = yes ] && return
374 message_echo "INFO: Terminated for mounting file systems because this utility was executed at a virtual (chroot or jail) environment."
375 message_echo "Execute"
376 basedir=`fs_get_system_basedir`
379 message_echo " $basedir${SHAREDIR}/bin/portsreinstall-chroot-mount"
380 message_echo "at the grand host environment."
382 message_echo " \$BASEDIR${SHAREDIR}/bin/portsreinstall-chroot-mount"
383 message_echo "at the grand host environment, where \$BASEDIR denotes the base directory of this virtual environment."
385 message_echo "After its successful execution, rerun"
386 if [ -n "$COMMAND_RESTART" ]
388 message_echo " ${APPNAME} $COMMAND_RESTART"
390 message_echo " ${APPNAME}"
393 [ $TEMP_IN_TRAP = no ] || temp_terminate_process
397 # ============= Generate a custom fstab file for the builder chroot environment =============
400 local stage systembase_target systembase_mp
402 systembase_target=`fs_system_base_in_target`
405 systembase_mp=`fs_system_base_in_mp_creation`
408 systembase_mp=`fs_system_base_in_mp_reference`
411 fs_safeguard_basedir "$opt_basedir"
414 type=`echo "$srcline" | cut -f 1`
415 target=`echo "$srcline" | cut -f 2`
416 directory=`echo "$srcline" | cut -f 3`
417 opt=`echo "$srcline" | cut -f 4`
418 [ "x$type" = xnullfs -o "x$type" = xunionfs ] && target=$systembase_target/$target
419 target=`str_regularize_df_path "$target"`
420 mp=`str_regularize_df_path "$systembase_mp/$opt_basedir/builder/$directory"`
421 opt=`echo "$opt" | sed 's/[[:space:]]/\\040/g'`
422 echo "$target $mp $type $opt 0 0"
423 done < ${DBDIR}/mount_manifest > ${DBDIR}/fstab
426 # ============= Mount the file systems for the builder chroot environment if not yet =============
430 if fs_chk_mount > /dev/null
432 message_echo "The builder chroot environment is already mounted."
436 fs_terminate_if_mount_unavailable
437 message_section_title "Mounting the file systems for builder chroot environment."
440 if ! mount -F "${DBDIR}/fstab" -a || ! remining=`fs_chk_mount`
442 cp "${DBDIR}/fstab" "${DBDIR}/fstab-mount-err"
443 message_echo "ERROR: Failed to mount the file systems. The followings remain unmounted:" >&2
444 if [ $opt_batch_mode = no ]
446 [ -n "$remining" ] || remining=`fs_chk_mount` || :
451 message_echo "Mounting done."
455 # ============= Check whether the file systems for the builder chroot environment are all unmounted or destroyed =============
458 local systembase_target systembase_mp tmp_remains
459 [ -e "${DBDIR}/mount_manifest" ] || return 0
460 systembase_target=`fs_system_base_in_target` || return 0
461 systembase_mp=`fs_system_base_in_mp_reference`
462 tmp_remains=${TMPDIR}/fs_chk_unmount:remains
463 rm -rf "$tmp_remains"
464 tail -r "${DBDIR}/mount_manifest" | while read srcline
466 type=`echo "$srcline" | cut -f 1`
467 target=`echo "$srcline" | cut -f 2`
468 [ "x$type" = xnullfs -o "x$type" = xunionfs ] && target=$systembase_target/$target
469 directory=`echo "$srcline" | cut -f 3`
470 opt=`echo "$srcline" | cut -f 4`
471 mp=`str_regularize_df_path "$systembase_mp/$opt_basedir/builder/$directory"`
472 real_mp=`realpath "$mp" 2> /dev/null` || continue
473 fs_chk_mounted "$type" "$target" "$real_mp" || continue
474 str_regularize_df_path "$mp" >> $tmp_remains
476 ! cat "$tmp_remains" 2> /dev/null
479 # ============= Terminate when the file systems for the builder chroot environment cannot be unmounted =============
480 fs_terminate_if_unmount_unavailable ()
482 local systembase systembase_saved
483 systembase=`fs_get_current_systembase`
484 systembase_saved=`fs_get_mounttime_systembase` || return 0
485 fs_chk_unmount > /dev/null && return
486 if [ "x$systembase" = "x$systembase_saved" ]
488 fs_chk_mount_privilege && return
489 elif [ -n "$systembase" ]
491 temp_terminate_process ()
493 message_echo "ERROR: Cannot unmount because the current file systems were mounted from inside the virtual (chroot or jail) environment." >&2
494 message_echo "INFO: Instead of this command, unmount from inside the virtual (chroot or jail) environment."
496 [ $TEMP_IN_TRAP = no ] || temp_terminate_process
499 temp_terminate_process ()
503 [ $opt_batch_mode = yes ] && return
505 message_echo "INFO: Terminated for unmounting file systems because this utility was executed at a virtual (chroot or jail) environment."
506 message_echo "Execute"
507 basedir=`fs_get_system_basedir`
510 message_echo " $basedir${SHAREDIR}/bin/portsreinstall-chroot-mount unmount"
511 message_echo "at the grand host environment."
513 message_echo " \$BASEDIR${SHAREDIR}/bin/portsreinstall-chroot-mount unmount"
514 message_echo "at the grand host environment, where \$BASEDIR denotes the base directory of this virtual environment."
516 message_echo "After its successful execution, rerun"
517 if [ -n "$COMMAND_RESTART" ]
519 message_echo " ${APPNAME} $COMMAND_RESTART"
521 message_echo " ${APPNAME}"
524 [ $TEMP_IN_TRAP = no ] || temp_terminate_process
528 # ============= Unmount file systems for the chroot environment =============
531 local systembase_access systembase_saved remining
532 systembase_access=`fs_system_base_in_mp_access`
533 ! fs_get_mounttime_systembase > /dev/null && return
534 fs_terminate_if_unmount_unavailable
535 [ ! -d "$systembase_access/$opt_basedir"/builder ] && return
536 [ ! -e "${DBDIR}/mount_manifest" ] && return
537 if fs_chk_unmount > /dev/null
539 if [ $TEMP_IN_TRAP = no ]
541 message_echo "The builder chroot environment is already unmounted."
546 if [ $TEMP_IN_TRAP = no ]
548 message_section_title "Unmounting the file systems for builder chroot."
551 message_echo "Unmounting the file systems for builder chroot."
554 umount -F "${DBDIR}/fstab" -af 2> /dev/null || :
555 itrial=${FS_UNMOUNT_RETIAL_MAXNUM}
556 while [ $itrial -gt 0 ]
558 remining=`fs_chk_unmount` && break
559 echo -n "$remining" | str_regularize_df_path_filter | while read remining_mp
561 umount -f "$remining_mp" || :
563 message_echo "(Retrying to unmount the file systems...)" >&2
564 sleep ${FS_UNMOUNT_RETIAL_WAIT}
565 umount -F "${DBDIR}/fstab" -af 2> /dev/null || :
566 itrial=$(($itrial-1))
570 message_echo "WARNING: Failed to unmount the file systems. Some of them remain mounted." >&2
573 rm -f "${DBDIR}/fs_systembase"
574 message_echo "Unmounting done."
578 # ============= Destroy the chroot environment =============
581 fs_chk_safety_basedir "$opt_basedir" || return 0
582 [ ! -d "$opt_basedir" ] && return
584 chflags -R noschg "$opt_basedir"
585 rm -rf "$opt_basedir"