OSDN Git Service

Edited the man page of portsreinstall-chroot to reflect the new features.
[portsreinstall/current.git] / lib / chroot / libfs.sh
1 #!/bin/sh -e
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 # ==============================================================================
9
10 FS_UNMOUNT_RETIAL_MAXNUM=5      # Number of retrial of unmounting
11 FS_UNMOUNT_RETIAL_WAIT=3        # Wait seconds in retrial of unmounting
12
13 # ============= Check the safety of the base directory of builder chroot environment =============
14 fs_chk_safety_basedir ()
15 {
16         local basedir
17         basedir=$1
18         [ -n "$basedir" ] || return
19         case "$basedir" in
20         /|/bin|/boot|/compat|/dev|/entropy|/etc|/home|/host|/lib|/libexec|/net|/proc|/rescue|/root|/sbin|/sys|/tmp|/usr|/var)
21                 return 1
22                 ;;
23         esac
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
32         :
33 }
34
35 # ============= Safeguard of the base directory of builder chroot environment =============
36 fs_safeguard_basedir ()
37 {
38         local basedir
39         basedir=$1
40         fs_chk_safety_basedir "$basedir" && return
41         message_echo "ERROR: The base directory [$opt_basedir] is not safe." >&2
42         exit 1
43 }
44
45 # ============= Build the file systems for the builder chroot environment =============
46 fs_build_chroot ()
47 {
48         local systembase
49         systembase=$1
50         [ -e "${DBDIR}/mount_manifest" ] && return
51         message_echo "Building the file systems for builder chroot environment (if not yet)."
52         fs_safeguard_basedir "$opt_basedir"
53         fs_unmount "$systembase" || return
54         mkdir -p "$systembase/$opt_basedir"
55         # Prescan the f file system of the original environment
56         cp /dev/null "${TMPDIR}/fs_build_chroot:directories"
57         (
58                 {
59                         echo bin compat etc lib libexec root sbin sys usr var | tr ' ' '\n'
60                         echo "$opt_extra_dirs" | tr "$opt_extra_dirs_delim" '\n'
61                 } | grep -v '^[[:space:]]*$' | sort -u > ${TMPDIR}/fs_build_chroot:sys_dirs
62                 sysdirs_ptn="^/*(`str_escape_regexp_filter < ${TMPDIR}/fs_build_chroot:sys_dirs | tr '\n' '|' | sed 's/\|$//'`)/+"
63                 while read directory
64                 do
65                         [ -e "$systembase"/$directory ] || continue
66                         if [ -L "$systembase"/$directory ]
67                         then
68                                 src_mountpoint_real=`realpath "$systembase"/$directory`
69                                         printf '%s\t%s\n' link $directory >> ${TMPDIR}/fs_build_chroot:directories
70                                 if ! echo "$src_mountpoint_real/" | grep -qE "$sysdirs_ptn"
71                                 then
72                                         printf '%s\t%s\n' real "$src_mountpoint_real" >> ${TMPDIR}/fs_build_chroot:directories
73                                         tmpdir_descendant=${TMPDIR}/fs_build_chroot:descendant/$src_mountpoint_real
74                                         mkdir -p "$tmpdir_descendant"
75                                         fs_get_descendant_mount_info "$systembase/$src_mountpoint_real" > $tmpdir_descendant/list
76                                 fi
77                         elif [ -d "$systembase"/$directory ]
78                         then
79                                 printf '%s\t%s\n' real $directory >> ${TMPDIR}/fs_build_chroot:directories
80                                 tmpdir_descendant=${TMPDIR}/fs_build_chroot:descendant/$directory
81                                 mkdir -p "$tmpdir_descendant"
82                                 fs_get_descendant_mount_info "$systembase/$directory" > $tmpdir_descendant/list
83                         fi
84                 done < ${TMPDIR}/fs_build_chroot:sys_dirs
85         )
86         # Prepare the grand base of the chroot environment
87         (
88                 cd "$systembase/$opt_basedir"
89                 for directory in builder mask store
90                 do
91                         [ -d $directory ] || mkdir $directory
92                 done
93         )
94         # Directories to share with the host environment by nullfs
95         if [ "x$opt_share_port_pkgs_dirs" = xyes ]
96         then
97                 echo "$PORTSDIR"
98                 echo "$PORTSNAP_WORKDIR"
99                 echo "$PKGNG_PKG_CACHEDIR"
100         fi | str_regularize_df_path_filter | grep -v '^[[:space:]]*$' | sort -u > ${DBDIR}/shared_dirs.lst
101         str_escape_regexp_filter < ${DBDIR}/shared_dirs.lst | sed 's|^|^|;s|$|\/|' > ${TMPDIR}/fs_build_chroot:shared_dirs.regexp
102         cp /dev/null "${TMPDIR}/fs_build_chroot:shared_dirs:added"
103         # Build target directories and the manifest for mounting
104         cp /dev/null "${DBDIR}/mount_manifest.tmp"
105         (
106                 cd "$systembase/$opt_basedir"/builder
107                 while read srcline
108                 do
109                         type=`echo "$srcline" | cut -f 1`
110                         directory=`echo "$srcline" | cut -f 2`
111                         case $type in
112                         link )
113                                 [ -e "$directory" -o -L "$directory" ] || cp -RpP "$systembase/$directory" .
114                                 ;;
115                         real )
116                                 mkdir -p "./$directory"
117                                 masktarget=$systembase/$opt_basedir/mask/$directory
118                                 mkdir -p "$masktarget"
119                                 printf '%s\t%s\t%s\t%s\n' nullfs "$systembase"/$directory $directory ro >> ${DBDIR}/mount_manifest.tmp
120                                 printf '%s\t%s\t%s\t%s\n' unionfs "$masktarget" $directory rw,noatime >> ${DBDIR}/mount_manifest.tmp
121                                 while read srcline
122                                 do
123                                         fs=`echo "$srcline" | cut -f 1`
124                                         mp=`echo "$srcline" | cut -f 2 | str_regularize_df_path_filter`
125                                         relative=`echo "$srcline" | cut -f 3 | str_regularize_df_path_filter`
126                                         rm -f "${TMPDIR}/fs_build_chroot:shared_dirs:is_under"
127                                         while read -r shared_path_regexp
128                                         do
129                                                 echo "/$directory/$relative/" | grep -qE "$shared_path_regexp" || continue
130                                                 grep -qFx "/$directory/$relative/" "${DBDIR}/shared_dirs.lst" || continue
131                                                 if  ! grep -qFx "/$directory/$relative/" "${TMPDIR}/fs_build_chroot:shared_dirs:added"
132                                                 then
133                                                         echo "/$directory/$relative/" >> ${TMPDIR}/fs_build_chroot:shared_dirs:added
134                                                         mp_share=`realpath "$systembase/$directory/$relative"`
135                                                         printf '%s\t%s\t%s\t%s\n' nullfs "$mp_share" "$directory/$relative" rw >> ${DBDIR}/mount_manifest.tmp
136                                                 fi
137                                                 touch "${TMPDIR}/fs_build_chroot:shared_dirs:is_under"
138                                         done < ${TMPDIR}/fs_build_chroot:shared_dirs.regexp
139                                         case $fs in
140                                                 normal )
141                                                         masktarget=$systembase/$opt_basedir/mask/$directory/$relative
142                                                         mkdir -p "$masktarget"
143                                                         if [ -e "${TMPDIR}/fs_build_chroot:shared_dirs:is_under" ]
144                                                         then
145                                                                 printf '%s\t%s\t%s\t%s\n' nullfs "$mp" "$directory/$relative" rw >> ${DBDIR}/mount_manifest.tmp
146                                                         else
147                                                                 printf '%s\t%s\t%s\t%s\n' nullfs "$mp" "$directory/$relative" ro >> ${DBDIR}/mount_manifest.tmp
148                                                                 printf '%s\t%s\t%s\t%s\n' unionfs "$masktarget" "$directory/$relative" rw,noatime >> ${DBDIR}/mount_manifest.tmp
149                                                         fi
150                                                         ;;
151                                                 devfs )
152                                                         printf '%s\t%s\t%s\t%s\n' devfs devfs "$directory/$relative" rw >> ${DBDIR}/mount_manifest.tmp
153                                                         ;;
154                                                 fdescfs )
155                                                         printf '%s\t%s\t%s\t%s\n' fdescfs fdesc "$directory/$relative" rw >> ${DBDIR}/mount_manifest.tmp
156                                                         ;;
157                                                 procfs )
158                                                         printf '%s\t%s\t%s\t%s\n' procfs proc "$directory/$relative" rw >> ${DBDIR}/mount_manifest.tmp
159                                                         ;;
160                                                 linprocfs )
161                                                         printf '%s\t%s\t%s\t%s\n' linprocfs linproc "$directory/$relative" rw >> ${DBDIR}/mount_manifest.tmp
162                                                         ;;
163                                                 tmpfs )
164                                                         printf '%s\t%s\t%s\t%s\n' tmpfs tmpfs "$directory/$relative" rw,mode=1777 >> ${DBDIR}/mount_manifest.tmp
165                                                         ;;
166                                         esac
167                                 done < ${TMPDIR}/fs_build_chroot:descendant/$directory/list
168                                 ;;
169                         esac
170                 done < ${TMPDIR}/fs_build_chroot:directories
171                 for directory in dev proc tmp 
172                 do
173                         [ -e $directory ] || mkdir $directory
174                 done
175                 printf '%s\t%s\t%s\t%s\n' devfs devfs dev rw >> ${DBDIR}/mount_manifest.tmp
176                 printf '%s\t%s\t%s\t%s\n' fdescfs fdesc dev/fd rw >> ${DBDIR}/mount_manifest.tmp
177                 printf '%s\t%s\t%s\t%s\n' procfs proc proc rw >> ${DBDIR}/mount_manifest.tmp
178                 printf '%s\t%s\t%s\t%s\n' tmpfs tmpfs tmp rw,mode=1777 >> ${DBDIR}/mount_manifest.tmp
179                 mkdir -p ."${PROGRAM}"
180                 cd "$systembase/$opt_basedir/mask"
181                 if [ ! -e root/.cshrc ]
182                 then
183                         tmp_cshrc=${TMPDIR}/fs_build_chroot:.cshrc
184                         [ -d root ] || mkdir root
185                         if [ -e "$systembase"/root/.cshrc ]
186                         then
187                                 cp -p "$systembase"/root/.cshrc "$tmp_cshrc"
188                                 cp -p "$systembase"/root/.cshrc "root/.cshrc.bak-${APPNAME}"
189                         elif [ -e "$systembase"/usr/share/skel/dot.cshrc ]
190                         then
191                                 cp -p "$systembase"/usr/share/skel/dot.cshrc "$tmp_cshrc"
192                                 touch "root/.cshrc.bak-${APPNAME}"
193                         else
194                                 cp /dev/null "$tmp_cshrc"
195                         fi
196                         echo >> $tmp_cshrc
197                         echo 'set prompt="%N@[builder]%m:%~ %# "' >> $tmp_cshrc
198                         mv "$tmp_cshrc" root/.cshrc
199                 fi
200                 printf '%s\t%s\t%s\t%s\n' nullfs "$systembase/$opt_basedir"/store ".${PROGRAM}" rw >> ${DBDIR}/mount_manifest.tmp
201         )
202         mv "${DBDIR}/mount_manifest.tmp" "${DBDIR}/mount_manifest"
203 }
204
205 # ============= Check whether the file systems for the builder chroot environment are all mounted =============
206 fs_chk_mount ()
207 {
208         local systembase
209         systembase=$1
210         [ -e "${DBDIR}/mount_manifest" ] || return
211         rm -rf ${TMPDIR}/fs_chk_mount:remains
212         while read srcline
213         do
214                 type=`echo "$srcline" | cut -f 1`
215                 target=`echo "$srcline" | cut -f 2`
216                 [ "x$type" = xnullfs -o "x$type" = xunionfs ] && target=$systembase/$target
217                 directory=`echo "$srcline" | cut -f 3`
218                 opt=`echo "$srcline" | cut -f 4`
219                 if ! fs_chk_mounted "$type" "$target" "$systembase/$opt_basedir/builder/$directory"
220                 then
221                         touch "${TMPDIR}"/fs_chk_mount:remains
222                         break
223                 fi
224         done < ${DBDIR}/mount_manifest
225         [ ! -e "${TMPDIR}"/fs_chk_mount:remains ]
226 }
227
228 # ============= Terminate when the file systems for the builder chroot environment cannot be mounted =============
229 fs_terminate_if_mount_unavailable ()
230 {
231         local systembase
232         systembase=$1
233         fs_chk_mount "$systembase" && return
234         fs_chk_mount_privilege && return
235         temp_terminate_process ()
236         {
237                 local errno basedir
238                 errno=${1:-0}
239                 [ $opt_batch_mode = yes ] && return
240                 if [ $errno -ne 2 ]
241                 then
242                         message_echo "Aborted by unexpected error" >&2
243                         exit
244                 fi
245                 message_echo
246                 message_echo "INFO: Terminated for mounting file systems because this utility was executed at a virtual (chroot or jail) environment."
247                 message_echo "Execute"
248                 basedir=`fs_get_system_basedir`
249                 if [ -n "$basedir" ]
250                 then
251                         message_echo "  $basedir${SHAREDIR}/bin/portsreinstall-chroot-mount"
252                         message_echo "at the grand host environment."
253                 else
254                         message_echo "  \$BASEDIR${SHAREDIR}/bin/portsreinstall-chroot-mount"
255                         message_echo "at the grand host environment, where \$BASEDIR denotes the base directory of this virtual environment."
256                 fi
257                 message_echo "After its successful execution, rerun"
258                 if [ -n "$COMMAND_RESTART" ]
259                 then
260                         message_echo "  ${APPNAME} $COMMAND_RESTART"
261                 else
262                         message_echo "  ${APPNAME}"
263                 fi
264         }
265         exit 2
266 }
267
268 # ============= Generate a custom fstab file for the builder chroot environment =============
269 fs_gen_fstab ()
270 {
271         local systembase
272         systembase=$1
273         [ "${DBDIR}/fstab" -nt "${DBDIR}/mount_manifest" ] && return
274         fs_safeguard_basedir "$opt_basedir"
275         while read srcline
276         do
277                 type=`echo "$srcline" | cut -f 1`
278                 target=`echo "$srcline" | cut -f 2`
279                 directory=`echo "$srcline" | cut -f 3`
280                 opt=`echo "$srcline" | cut -f 4`
281                 [ "x$type" = xnullfs -o "x$type" = xunionfs ] && target=$systembase/$target
282                 target=`str_regularize_df_path "$target"`
283                 mp=`str_regularize_df_path "$systembase/$opt_basedir/builder/$directory"`
284                 opt=`echo "$opt" | sed 's/[[:space:]]/\\040/g'`
285                 echo "$target $mp $type $opt 0 0"
286         done < ${DBDIR}/mount_manifest > ${DBDIR}/fstab
287 }
288
289 # ============= Mount the file systems for the builder chroot environment if not yet =============
290 fs_mount ()
291 {
292         local systembase
293         systembase=$1
294         message_echo "Mounting the file systems for builder chroot environment."
295         fs_gen_fstab
296         if ! mount -F "${DBDIR}/fstab" -a || ! fs_chk_mount "$systembase"
297         then
298                 message_echo "Error: Failed to mount the file systems. Some of them remain unmounted." >&2
299                 exit 1
300         fi
301         message_echo "Mounting done."
302 }
303
304 # ============= Check whether the file systems for the builder chroot environment are all unmounted or destroyed =============
305 fs_chk_unmount ()
306 {
307         local systembase
308         systembase=$1
309         [ -e "${DBDIR}/mount_manifest" ] || return 0
310         rm -rf ${TMPDIR}/fs_chk_unmount:remains
311         tail -r "${DBDIR}/mount_manifest" | while read srcline
312         do
313                 type=`echo "$srcline" | cut -f 1`
314                 target=`echo "$srcline" | cut -f 2`
315                 [ "x$type" = xnullfs -o "x$type" = xunionfs ] && target=$systembase/$target
316                 directory=`echo "$srcline" | cut -f 3`
317                 opt=`echo "$srcline" | cut -f 4`
318                 if fs_chk_mounted "$type" "$target" "$systembase/$opt_basedir/builder/$directory"
319                 then
320                         touch "${TMPDIR}"/fs_chk_unmount:remains
321                         break
322                 fi
323         done
324         [ ! -e "${TMPDIR}"/fs_chk_unmount:remains ]
325 }
326
327 # ============= Terminate when the file systems for the builder chroot environment cannot be unmounted =============
328 fs_terminate_if_unmount_unavailable ()
329 {
330         local systembase
331         systembase=$1
332         fs_chk_unmount "$systembase" && return
333         fs_chk_unmount_privilege && return
334         temp_terminate_process ()
335         {
336                 local errno basedir
337                 errno=${1:-0}
338                 [ $opt_batch_mode = yes ] && return
339                 if [ $errno -ne 3 ]
340                 then
341                         message_echo "Aborted by unexpected error" >&2
342                         exit
343                 fi
344                 message_echo
345                 message_echo "INFO: Terminated for unmounting file systems because this utility was executed at a virtual (chroot or jail) environment."
346                 message_echo "Execute"
347                 basedir=`fs_get_system_basedir`
348                 if [ -n "$basedir" ]
349                 then
350                         message_echo "  $basedir${SHAREDIR}/bin/portsreinstall-chroot-mount unmount"
351                         message_echo "at the grand host environment."
352                 else
353                         message_echo "  \$BASEDIR${SHAREDIR}/bin/portsreinstall-chroot-mount unmount"
354                         message_echo "at the grand host environment, where \$BASEDIR denotes the base directory of this virtual environment."
355                 fi
356                 message_echo "After its successful execution, rerun"
357                 if [ -n "$COMMAND_RESTART" ]
358                 then
359                         message_echo "  ${APPNAME} $COMMAND_RESTART"
360                 else
361                         message_echo "  ${APPNAME}"
362                 fi
363         }
364         exit 3
365 }
366
367 # ============= Unmount  file systems for the chroot environment =============
368 fs_unmount ()
369 {
370         local systembase
371         systembase=$1
372         [ ! -d "$systembase/$opt_basedir"/builder ] && return
373         [ -e "${DBDIR}/mount_manifest" ] || return 0
374         message_echo "Unmounting the file systems for builder chroot."
375         fs_gen_fstab
376         umount -F "${DBDIR}/fstab" -af 2> /dev/null || :
377         itrial=${FS_UNMOUNT_RETIAL_MAXNUM}
378         while [ $itrial -gt 0 ]
379         do
380                 fs_chk_unmount "$systembase" && break
381                 message_echo "(Retrying to unmount the file systems...)" >&2
382                 sleep ${FS_UNMOUNT_RETIAL_WAIT}
383                 umount -F "${DBDIR}/fstab" -af 2> /dev/null || :
384                 itrial=$(($itrial-1))
385         done
386         if [ $itrial -eq 0 ]
387         then
388                 message_echo "WARNING: Failed to unmount the file systems. Some of them remain mounted." >&2
389                 return 1
390         fi
391         message_echo "Unmounting done."
392 }
393
394 # ============= Destroy the chroot environment =============
395 fs_destroy ()
396 {
397         local systembase
398         systembase=$1
399         fs_chk_safety_basedir "$opt_basedir" || return 0
400         [ ! -d "$opt_basedir" ] && return
401         fs_terminate_if_unmount_unavailable "$systembase"
402         fs_unmount "$systembase" || return
403         chflags -R noschg "$opt_basedir"
404         rm -rf "$opt_basedir"
405 }