comparison sbin/ftjail @ 341:a204a7415d4a

"ftjail freebsd-update" is implemented. It supports custom mountpoints also.
author Franz Glasner <fzglas.hg@dom66.de>
date Sat, 03 Dec 2022 21:12:23 +0100
parents d3b5fe2712ca
children 89877869a665
comparison
equal deleted inserted replaced
340:d3b5fe2712ca 341:a204a7415d4a
42 42
43 copy-skel [-A] [-L] [-M MOUNTPOINT] [-P] [-u] SOURCE-DS SNAPSHOT-NAME TARGET-DS 43 copy-skel [-A] [-L] [-M MOUNTPOINT] [-P] [-u] SOURCE-DS SNAPSHOT-NAME TARGET-DS
44 44
45 build-etcupdate-current-tmpl DIRECTORY TARBALL 45 build-etcupdate-current-tmpl DIRECTORY TARBALL
46 46
47 freebsd-update [-k] [-o OLD-ORIGIN] DIRECTORY 47 freebsd-update [-k] [-o OLD-ORIGIN] DIRECTORY NEW-ORIGIN [ETCUPDATE-TARBALL]
48 48
49 ENVIRONMENT: 49 ENVIRONMENT:
50 50
51 All environment variables that affect "zfs" are effective also. 51 All environment variables that affect "zfs" are effective also.
52 52
872 fi 872 fi
873 } 873 }
874 874
875 875
876 #: 876 #:
877 #: Determine extra clone options with respect to the "mountpoint" property
878 #:
879 #: Args:
880 #: $1: the dataset
881 #:
882 #: Output (stdout)
883 #: The extra clone arguments
884 #:
885 #: Exit:
886 #: On unexpected source values
887 #:
888 _get_clone_extra_prop_for_mountpoint() {
889 local ds
890
891 local _mp_name _mp_property _mp_value _mp_source
892
893 ds="${1}"
894
895 zfs get -H mountpoint "${ds}" \
896 | {
897 IFS=$'\t' read -r _mp_name _mp_property _mp_value _mp_source
898 case "${_mp_source}" in
899 local)
900 echo -n "-o mountpoint=${_mp_value}"
901 ;;
902 default|inherited*)
903 ;;
904 temporary*|received*|'-'|none)
905 # XXX FIXME: Is this relevant on FreeBSD?
906 echo "ERROR: Unexpected SOURCE \"${_mp_source}\" for mountpoint at \`${_mp_value}'" 1>&2
907 exit 1
908 ;;
909 *)
910 echo "ERROR: Unexpected SOURCE for mountpoint property at \`${_mp_value}'" 1>&2
911 exit 1;
912 ;;
913 esac
914 if [ "${_mp_value}" != "${_directory}" ]; then
915 echo "WARNING: dataset is not mounted at its configured mountpoint but elsewhere (probably via \"mount -t zfs\")" 1>&2
916 fi
917 }
918 }
919
920
921 #:
922 #: Determine the "canmount" property for a dataset
923 #:
924 #: Args:
925 #: $1: the dataset
926 #:
927 #: Output (stdout):
928 #: The local value or "DEFAULT" for the (unset) default
929 #:
930 #: Exit:
931 #: On unexpected source values
932 #:
933 _get_canmount_setting_for_dataset() {
934 local ds
935
936 local _cm_name _cm_property _cm_value _cm_source
937
938 ds="${1}"
939
940 zfs get -H canmount "${ds}" \
941 | {
942 IFS=$'\t' read -r _cm_name _cm_property _cm_value _cm_source
943 case "${_cm_source}" in
944 local)
945 echo -n "canmount=${_cm_value}"
946 ;;
947 default)
948 echo -n "DEFAULT"
949 ;;
950 inherited|temporary*|received*|'-'|none)
951 # XXX FIXME: Is this relevant on FreeBSD?
952 echo "ERROR: Unexpected SOURCE \"${_cm_source}\" for canmount at \`${_cm_name}'" 1>&2
953 exit 1
954 ;;
955 *)
956 echo "ERROR: Unexpected SOURCE for canmount property at \`${_cm_name}'" 1>&2
957 exit 1;
958 ;;
959 esac
960 }
961 }
962
963
964 #:
877 #: Implement the "freebsd-update" command for a thin jail 965 #: Implement the "freebsd-update" command for a thin jail
878 #: 966 #:
879 command_freebsd_update() { 967 command_freebsd_update() {
880 local _directory 968 local _directory _new_origin _etcupdate_tarball
881 local _opt_keep _opt_old_origin 969 local _opt_keep _opt_old_origin
882 970
883 local _res _jailname _dir_mounts _dir_fn_fstab _dir_basename 971 local _res _jailname _dir_mounts _dir_fn_fstab _dir_basename _dir_fn_tldir
884 local _root_dataset _root_mountpoint _root_type _root_options 972 local _root_dataset _root_mountpoint _root_type _root_options
885 local _dummy _opt 973 local _clone_extra_props _canmount_prop
974 local _line _opt
886 local _root_readonly _root_origin 975 local _root_readonly _root_origin
887 976
888 _opt_keep="no" 977 _opt_keep="no"
889 _opt_old_origin="" 978 _opt_old_origin=""
890 while getopts "ko:" _opt ; do 979 while getopts "ko:" _opt ; do
902 done 991 done
903 shift $((OPTIND-1)) 992 shift $((OPTIND-1))
904 OPTIND=1 993 OPTIND=1
905 994
906 _directory="${1-}" 995 _directory="${1-}"
996 _new_origin="${2-}"
997 _etcupdate_tarball="${3-}"
907 998
908 [ -z "${_directory}" ] && { echo "ERROR: no directory given" 1>&2; return 2; } 999 [ -z "${_directory}" ] && { echo "ERROR: no directory given" 1>&2; return 2; }
909 [ -d "${_directory}" ] || { echo "ERROR: directory \`${_directory}' does not exist" 1>&2; exit 1; } 1000 [ -d "${_directory}" ] || { echo "ERROR: directory \`${_directory}' does not exist" 1>&2; return 1; }
1001
1002 [ -z "${_new_origin}" ] && { echo "ERROR: no new origin given" 1>&2; return 2; }
1003 zfs list -H -o name -t snapshot "${_new_origin}" >/dev/null || { echo "ERROR: new origin does not exist" 1>&2; return 1; }
1004 if [ -n "${_etcupdate_tarball}" ]; then
1005 [ -f "${_etcupdate_tarball}" ] || { echo "ERROR: given etcupdate tarball does not exist " 1>&2; return 1; }
1006 fi
910 1007
911 _dir_basename="$(basename ${_directory})" 1008 _dir_basename="$(basename ${_directory})"
912 1009
913 set +e 1010 set +e
914 _jailname=$(_get_jail_from_path "${_directory}")´ 1011 _jailname=$(_get_jail_from_path "${_directory}")´
915 _res=$? 1012 _res=$?
916 set -e 1013 set -e
917 if [ ${_res} -ne 2 ] ; then 1014 if [ ${_res} -ne 2 ] ; then
918 if [ ${_res} -ne 0 ] ; then 1015 if [ ${_res} -ne 0 ] ; then
919 exit ${_res} 1016 return ${_res}
920 else 1017 else
921 echo "ERROR: Please stop the \`${_jailname}' jail" >&2 1018 echo "ERROR: Please stop the \`${_jailname}' jail" >&2
922 exit 1 1019 return 1
923 fi 1020 fi
924 fi 1021 fi
925 _dir_mounts="$(_get_mounts_at_directory "${_directory}")" 1022 _dir_mounts="$(_get_mounts_at_directory "${_directory}")"
926 1023
927 # 1024 #
928 # Check preconditions thoroughly! 1025 # Check preconditions thoroughly!
929 # 1026 #
930 # Check that the first item/line is a read-only ZFS mount directly 1027 # Check that the first item/line is a read-only ZFS mount directly
931 # at the given directory. 1028 # at the given directory. This must also be its configured
1029 # mountpoint in ZFS.
932 # Also check that it is a clone proper. 1030 # Also check that it is a clone proper.
933 # 1031 #
934 IFS=' '$'\t' read -r _root_dataset _root_mountpoint _root_type _root_options _dummy <<EOF4tHGCSS 1032 IFS=' '$'\t' read -r _root_dataset _root_mountpoint _root_type _root_options _line <<EOF4tHGCSS
935 ${_dir_mounts} 1033 ${_dir_mounts}
936 EOF4tHGCSS 1034 EOF4tHGCSS
937 [ "${_root_mountpoint}" != "${_directory}" ] && { echo "ERROR: found root mountpoint does not match given directory" 1>&2; exit 1; } 1035 [ "${_root_mountpoint}" != "${_directory}" ] && { echo "ERROR: found root mountpoint does not match given directory" 1>&2; return 1; }
938 [ "${_root_type}" != "zfs" ] && { echo "ERROR: root mountpoint is not from a ZFS dataset" 1>&2; exit 1; } 1036 [ "${_root_type}" != "zfs" ] && { echo "ERROR: root mountpoint is not from a ZFS dataset" 1>&2; return 1; }
939 _root_readonly="$(zfs list -H -o readonly "${_root_dataset}")" 1037 _root_readonly="$(zfs list -H -o readonly "${_root_dataset}")"
940 [ "${_root_readonly}" != "on" ] && { echo "ERROR: the root dataset is not mounted read-only" 1>&2; exit 1; } 1038 [ "${_root_readonly}" != "on" ] && { echo "ERROR: the root dataset is not mounted read-only" 1>&2; return 1; }
941 _root_origin="$(zfs list -H -o origin "${_root_dataset}")" 1039 _root_origin="$(zfs list -H -o origin "${_root_dataset}")"
942 if [ -n "${_opt_old_origin}" ]; then 1040 if [ -n "${_opt_old_origin}" ]; then
943 [ "${_opt_old_origin}" != "${_root_origin}" ] && { echo "ERROR: origin mismatch" 1>&2; exit 1; } 1041 [ "${_opt_old_origin}" != "${_root_origin}" ] && { echo "ERROR: origin mismatch" 1>&2; return 1; }
944 else 1042 else
945 [ "${_root_origin}" = '-' ] && { echo "ERROR: the root dataset is not a ZFS clone" 1>&2; exit 1; } 1043 [ "${_root_origin}" = '-' ] && { echo "ERROR: the root dataset is not a ZFS clone" 1>&2; return 1; }
946 fi 1044 fi
1045
1046 # Determine we need to clone with a custom (non inherited) "mountpoint"
1047 _clone_extra_props="$(_get_clone_extra_prop_for_mountpoint "${_root_dataset}") "
1048 # Determine we need to clone with a custom (non inherited) "canmount"
1049 _canmount_prop="$(_get_canmount_setting_for_dataset "${_root_dataset}")"
1050
947 # 1051 #
948 # XXX FIXME: should we check that _root_options equals "ro" or 1052 # XXX FIXME: should we check that _root_options equals "ro" or
949 # start with "ro," 1053 # start with "ro,"
950 # _root_origin="$(zfs list -H -o origin "${_root_dataset}")" 1054 # _root_origin="$(zfs list -H -o origin "${_root_dataset}")"
951 1055
952 _dir_fn_fstab="$(env TMPDIR=/var/tmp mktemp -t ftjail-fstab.${_dir_basename})" 1056 _dir_fn_fstab="$(env TMPDIR=/var/tmp mktemp -t ftjail_${_dir_basename}.fstab)"
953 echo -n "${_dir_mounts}" >>"${_dir_fn_fstab}" 1057 echo -n "${_dir_mounts}" >>"${_dir_fn_fstab}"
954 1058
1059 _dir_fn_tldir="$(env TMPDIR=/var/tmp mktemp -t ftjail_${_dir_basename}.tldir)"
1060 find "${_directory}" -depth 1 -type d 2>/dev/null | sort >>"${_dir_fn_tldir}"
1061
955 # Unmount in reverse order: unmount can do it for us 1062 # Unmount in reverse order: unmount can do it for us
956 umount -a -F "${_dir_fn_fstab}" -v || exit 1 1063 echo "Unmounting all datasets mounted at \`${_directory}'"
1064 umount -a -F "${_dir_fn_fstab}" -v
957 1065
958 # 1066 #
959 # XXX TBD: Hooks to create some new top-level dirs (/srv /proc et 1067 # XXX TBD: Hooks to create some new top-level dirs (/srv /proc et
960 # al.) if needed: clone RW, mount, make the dirs, 1068 # al.) if needed: clone RW, mount, make the dirs,
961 # umount, make the clone RO and continue "normally" by 1069 # umount, make the clone RO and continue "normally" by
962 # completely mounting the stored fstab. 1070 # completely mounting the stored fstab.
963 # 1071 #
964 1072
1073 #
1074 # Destroy the current read-only root clone and make a new clone based
1075 # on the given new origin.
1076 # The new clone temporarily is RW and is not to be mounted automatically.
1077 # These both properties are set again below after the new base is
1078 # adjusted properly.
1079 #
1080 echo "Destroying the cloned root dataset \`${_root_dataset}'"
1081 zfs destroy -v "${_root_dataset}"
1082 echo "Cloning a new root dataset \`${_root_dataset}' from new origin \`${_new_origin}'"
1083 zfs clone -o readonly=off -o canmount=noauto ${_clone_extra_props} "${_new_origin}" "${_root_dataset}"
1084 #
1085 # NOTE: Always mount with "mount -t zfs" because a custom
1086 # mountpoint is not reflected in the "mountpoint"
1087 # property. So in scripts to be sure to unmount and re-mount
1088 # at the same location always use "mount -t zfs".
1089 #
1090 echo "Remounting only the root dataset at \`${_directory}'"
1091 [ ! -d "${_directory}" ] && mkdir "${_directory}"
1092 mount -t zfs "${_root_dataset}" "${_directory}"
1093 #
1094 # Re-create all currently missing top-level dirs (aka mountpoint)
1095 # in the new clone. Most probably they serve as mountpoints for other
1096 # datasets.
1097 #
1098 # XXX FIXME: Re-create the current mode bits and/or ACLs also.
1099 # But most probably they are set properly in the mounted
1100 # datasets.
1101 #
1102 echo "Recreating missing top-level directories"
1103 cat "${_dir_fn_tldir}" \
1104 | {
1105 while IFS='' read -r _line ; do
1106 if [ ! -d "${_line}" ]; then
1107 echo "Recreating top-level directory: ${_line}"
1108 mkdir "${_line}"
1109 fi
1110 done
1111 }
1112 echo "Unmounting the new root dataset"
1113 umount "${_directory}"
1114 echo "Re-setting some ZFS properties on the new cloned dataset"
1115 zfs set readonly=on "${_root_dataset}"
1116 #
1117 # Copy "canmount" properly last because it has been set to "noauto"
1118 # temporarily.
1119 #
1120 if [ -n "${_canmount_prop}" ]; then
1121 if [ "${_canmount_prop}" = "DEFAULT" ]; then
1122 #
1123 # "zfs inherit" is not possible for "canmount".
1124 # Use "inherit -S" to simulate a reset to "default" somewhat
1125 #
1126 # See also: https://github.com/openzfs/zfs/issues/5733
1127 #
1128 zfs inherit -S canmount "${_root_dataset}"
1129 else
1130 zfs set "${_canmount_prop}" "${_root_dataset}"
1131 fi
1132 fi
1133
1134 # Mount again
1135 echo "Mounting all datasets rooted at \`${_directory}'"
1136 [ ! -d "${_directory}" ] && mkdir "${_directory}"
1137 mount -a -F "${_dir_fn_fstab}" -v
1138
1139 # Update configs
1140 if [ -n "${_etcupdate_tarball}" ]; then
1141 echo "Calling etcupdate for DESTDIR=${_directory}"
1142 etcupdate -D "${_directory}" -t "${_etcupdate_tarball}"
1143 fi
1144
965 if [ "${_opt_keep}" != "yes" ]; then 1145 if [ "${_opt_keep}" != "yes" ]; then
1146 echo "Cleaning up..."""
1147 [ -n "${_dir_fn_tldir}" } && [ -f "${_dir_fn_tldir}" ] && rm -f "${_dir_fn_tldir}"
966 [ -n "${_dir_fn_fstab}" ] && [ -f "${_dir_fn_fstab}" ] && rm -f "${_dir_fn_fstab}" 1148 [ -n "${_dir_fn_fstab}" ] && [ -f "${_dir_fn_fstab}" ] && rm -f "${_dir_fn_fstab}"
967 fi 1149 fi
1150 echo "Done."
968 } 1151 }
969 1152
970 1153
971 # 1154 #
972 # Global option handling 1155 # Global option handling