##/bin/bash # Routines for populating a scratch fs, and helpers to exercise an FS # once it's been fuzzed. #----------------------------------------------------------------------- # Copyright (c) 2015 Oracle. All Rights Reserved. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Contact information: Silicon Graphics, Inc., 1500 Crittenden Lane, # Mountain View, CA 94043, USA, or: http://www.sgi.com #----------------------------------------------------------------------- _require_xfs_io_command "falloc" _require_xfs_io_command "fpunch" _require_xfs_db_blocktrash_z_command() { test "${FSTYP}" = "xfs" || _notrun "cannot run xfs_db on ${FSTYP}" $XFS_DB_PROG -x -f -c 'blocktrash -z' "${TEST_DEV}" | grep -q 'nothing on stack' || _notrun "blocktrash -z not supported" } # Attempt to make files of "every" format for data, dirs, attrs etc. # (with apologies to Eric Sandeen for mutating xfser.sh) # Create a large directory __populate_create_dir() { name="$1" nr="$2" missing="$3" mkdir -p "${name}" seq 0 "${nr}" | while read d; do creat=mkdir test "$((d % 20))" -eq 0 && creat=touch $creat "${name}/$(printf "%.08d" "$d")" done test -z "${missing}" && return seq 1 2 "${nr}" | while read d; do rm -rf "${name}/$(printf "%.08d" "$d")" done } # Add a bunch of attrs to a file __populate_create_attr() { name="$1" nr="$2" missing="$3" touch "${name}" seq 0 "${nr}" | while read d; do setfattr -n "user.$(printf "%.08d" "$d")" -v "$(printf "%.08d" "$d")" "${name}" done test -z "${missing}" && return seq 1 2 "${nr}" | while read d; do setfattr -x "user.$(printf "%.08d" "$d")" "${name}" done } # Fill up 60% of the remaining free space __populate_fill_fs() { dir="$1" pct="$2" test -z "${pct}" && pct=60 SRC_SZ="$(du -ks "${SRCDIR}" | cut -f 1)" FS_SZ="$(( $(stat -f "${dir}" -c '%a * %S') / 1024 ))" NR="$(( (FS_SZ * ${pct} / 100) / SRC_SZ ))" test "${NR}" -lt 1 && NR=1 seq 1 "${NR}" | while read nr; do cp -pRdu "${SRCDIR}" "${dir}/test.${nr}" >> $seqres.full 2>&1 done } # Populate an XFS on the scratch device with (we hope) all known # types of metadata block _scratch_xfs_populate() { _scratch_mount blksz="$(stat -f -c '%s' "${SCRATCH_MNT}")" dblksz="$(xfs_info "${SCRATCH_MNT}" | grep naming.*bsize | sed -e 's/^.*bsize=//g' -e 's/\([0-9]*\).*$/\1/g')" leaf_lblk="$((32 * 1073741824 / blksz))" node_lblk="$((64 * 1073741824 / blksz))" # Data: # Regular files # - FMT_EXTENTS echo "+ extents file" $XFS_IO_PROG -f -c "pwrite -S 0x61 0 ${blksz}" "${SCRATCH_MNT}/S_IFREG.FMT_EXTENTS" # - FMT_BTREE echo "+ btree extents file" nr="$((blksz * 2 / 16))" $XFS_IO_PROG -f -c "pwrite -S 0x62 0 $((blksz * nr))" "${SCRATCH_MNT}/S_IFREG.FMT_BTREE" for i in $(seq 1 2 ${nr}); do $XFS_IO_PROG -f -c "fpunch $((i * blksz)) ${blksz}" "${SCRATCH_MNT}/S_IFREG.FMT_BTREE" done # Directories # - INLINE echo "+ inline dir" __populate_create_dir "${SCRATCH_MNT}/S_IFDIR.FMT_INLINE" 1 # - BLOCK echo "+ block dir" __populate_create_dir "${SCRATCH_MNT}/S_IFDIR.FMT_BLOCK" "$((dblksz / 40))" # - LEAF echo "+ leaf dir" __populate_create_dir "${SCRATCH_MNT}/S_IFDIR.FMT_LEAF" "$((dblksz / 12))" # - NODE echo "+ node dir" __populate_create_dir "${SCRATCH_MNT}/S_IFDIR.FMT_NODE" "$((16 * dblksz / 40))" true # - BTREE __populate_create_dir "${SCRATCH_MNT}/S_IFDIR.FMT_BTREE" "$((128 * dblksz / 40))" true # Symlinks # - FMT_LOCAL echo "+ inline symlink" ln -s target "${SCRATCH_MNT}/S_IFLNK.FMT_LOCAL" # - FMT_EXTENTS echo "+ extents symlink" ln -s "$(perl -e 'print "x" x 1023;')" "${SCRATCH_MNT}/S_IFLNK.FMT_EXTENTS" # Char & block echo "+ special" mkdir devices mknod "${SCRATCH_MNT}/S_IFCHR" c 1 1 mknod "${SCRATCH_MNT}/S_IFBLK" c 1 1 # Attribute formats # LOCAL echo "+ local attr" __populate_create_attr "${SCRATCH_MNT}/ATTR.FMT_LOCAL" 1 # LEAF echo "+ leaf attr" __populate_create_attr "${SCRATCH_MNT}/ATTR.FMT_LEAF" "$((blksz / 40))" # NODE echo "+ node attr" __populate_create_attr "${SCRATCH_MNT}/ATTR.FMT_NODE" "$((8 * blksz / 40))" # BTREE echo "+ btree attr" __populate_create_attr "${SCRATCH_MNT}/ATTR.FMT_BTREE" "$((64 * blksz / 40))" true # FMT_EXTENTS with a remote less-than-a-block value echo "+ attr extents with a remote less-than-a-block value" touch "${SCRATCH_MNT}/ATTR.FMT_EXTENTS_REMOTE3K" $XFS_IO_PROG -f -c "pwrite -S 0x43 0 3k" "${SCRATCH_MNT}/attrvalfile" > /dev/null attr -q -s user.remotebtreeattrname "${SCRATCH_MNT}/ATTR.FMT_EXTENTS_REMOTE3K" < "${SCRATCH_MNT}/attrvalfile" # FMT_EXTENTS with a remote block-size value echo "+ attr extents with a remote one-block value" touch "${SCRATCH_MNT}/ATTR.FMT_EXTENTS_REMOTE4K" $XFS_IO_PROG -f -c "pwrite -S 0x44 0 4k" "${SCRATCH_MNT}/attrvalfile" > /dev/null attr -q -s user.remotebtreeattrname "${SCRATCH_MNT}/ATTR.FMT_EXTENTS_REMOTE4K" < "${SCRATCH_MNT}/attrvalfile" rm -rf "${SCRATCH_MNT}/attrvalfile" # Make an unused inode echo "+ empty file" touch "${SCRATCH_MNT}/unused" $XFS_IO_PROG -f -c 'fsync' "${SCRATCH_MNT}/unused" rm -rf "${SCRATCH_MNT}/unused" # Copy some real files (xfs tests, I guess...) echo "+ real files" #__populate_fill_fs "${SCRATCH_MNT}" 40 cp -pRdu --reflink=always "${SCRATCH_MNT}/S_IFREG.FMT_BTREE" "${SCRATCH_MNT}/S_IFREG.FMT_BTREE.REFLINK" 2> /dev/null umount "${SCRATCH_MNT}" } # Populate an ext4 on the scratch device with (we hope) all known # types of metadata block _scratch_ext4_populate() { _scratch_mount blksz="$(stat -f -c '%s' "${SCRATCH_MNT}")" dblksz="${blksz}" leaf_lblk="$((32 * 1073741824 / blksz))" node_lblk="$((64 * 1073741824 / blksz))" # Data: # Regular files # - FMT_INLINE echo "+ inline file" $XFS_IO_PROG -f -c "pwrite -S 0x61 0 1" "${SCRATCH_MNT}/S_IFREG.FMT_INLINE" # - FMT_EXTENTS echo "+ extents file" $XFS_IO_PROG -f -c "pwrite -S 0x61 0 ${blksz}" "${SCRATCH_MNT}/S_IFREG.FMT_EXTENTS" # - FMT_ETREE echo "+ extent tree file" nr="$((blksz * 2 / 12))" $XFS_IO_PROG -f -c "pwrite -S 0x62 0 $((blksz * nr))" "${SCRATCH_MNT}/S_IFREG.FMT_ETREE" for i in $(seq 1 2 ${nr}); do $XFS_IO_PROG -f -c "fpunch $((i * blksz)) ${blksz}" "${SCRATCH_MNT}/S_IFREG.FMT_ETREE" done # Directories # - INLINE echo "+ inline dir" __populate_create_dir "${SCRATCH_MNT}/S_IFDIR.FMT_INLINE" 1 # - BLOCK echo "+ block dir" __populate_create_dir "${SCRATCH_MNT}/S_IFDIR.FMT_BLOCK" "$((dblksz / 24))" # - HTREE echo "+ htree dir" __populate_create_dir "${SCRATCH_MNT}/S_IFDIR.FMT_HTREE" "$((4 * dblksz / 24))" # Symlinks # - FMT_LOCAL echo "+ inline symlink" ln -s target "${SCRATCH_MNT}/S_IFLNK.FMT_LOCAL" # - FMT_EXTENTS echo "+ extents symlink" ln -s "$(perl -e 'print "x" x 1023;')" "${SCRATCH_MNT}/S_IFLNK.FMT_EXTENTS" # Char & block echo "+ special" mkdir devices mknod "${SCRATCH_MNT}/S_IFCHR" c 1 1 mknod "${SCRATCH_MNT}/S_IFBLK" c 1 1 # Attribute formats # LOCAL echo "+ local attr" __populate_create_attr "${SCRATCH_MNT}/ATTR.FMT_LOCAL" 1 # BLOCK echo "+ block attr" __populate_create_attr "${SCRATCH_MNT}/ATTR.FMT_BLOCK" "$((blksz / 40))" # Make an unused inode echo "+ empty file" touch "${SCRATCH_MNT}/unused" $XFS_IO_PROG -f -c 'fsync' "${SCRATCH_MNT}/unused" rm -rf "${SCRATCH_MNT}/unused" # Copy some real files (xfs tests, I guess...) echo "+ real files" __populate_fill_fs "${SCRATCH_MNT}" cp -pRdu --reflink=always "${SCRATCH_MNT}/S_IFREG.FMT_ETREE" "${SCRATCH_MNT}/S_IREG.FMT_ETREE.REFLINK" 2> /dev/null umount "${SCRATCH_MNT}" } # Find the inode number of a file __populate_find_inode() { name="$1" inode="$(stat -c '%i' "${name}")" echo "${inode}" } # Check data fork format of XFS file __populate_check_xfs_dformat() { dev="$1" inode="$2" format="$3" fmt="$($XFS_DB_PROG -c "inode ${inode}" -c 'p core.format' "${dev}" | sed -e 's/^.*(\([a-z]*\)).*$/\1/g')" test "${format}" = "${fmt}" || _fail "failed to create ino ${inode} dformat expected ${format} saw ${fmt}" } # Check attr fork format of XFS file __populate_check_xfs_aformat() { dev="$1" inode="$2" format="$3" fmt="$($XFS_DB_PROG -c "inode ${inode}" -c 'p core.aformat' "${dev}" | sed -e 's/^.*(\([a-z]*\)).*$/\1/g')" test "${format}" = "${fmt}" || _fail "failed to create ino ${inode} aformat expected ${format} saw ${fmt}" } # Check structure of XFS directory __populate_check_xfs_dir() { dev="$1" inode="$2" dtype="$3" (test -n "${leaf_lblk}" && test -n "${node_lblk}") || _fail "must define leaf_lblk and node_lblk before calling __populate_check_xfs_dir" datab=0 leafb=0 freeb=0 #echo "== check dir ${inode} type ${dtype}" ; $XFS_DB_PROG -x -c "inode ${inode}" -c "bmap" "${SCRATCH_DEV}" $XFS_DB_PROG -x -c "inode ${inode}" -c "dblock 0" -c "stack" "${SCRATCH_DEV}" | grep -q 'file data block is unmapped' || datab=1 $XFS_DB_PROG -x -c "inode ${inode}" -c "dblock ${leaf_lblk}" -c "stack" "${SCRATCH_DEV}" | grep -q 'file data block is unmapped' || leafb=1 $XFS_DB_PROG -x -c "inode ${inode}" -c "dblock ${node_lblk}" -c "stack" "${SCRATCH_DEV}" | grep -q 'file data block is unmapped' || freeb=1 case "${dtype}" in "shortform"|"inline"|"local") (test "${datab}" -eq 0 && test "${leafb}" -eq 0 && test "${freeb}" -eq 0) || _fail "failed to create ${dtype} dir ino ${inode} datab ${datab} leafb ${leafb} freeb ${freeb}" ;; "block") (test "${datab}" -eq 1 && test "${leafb}" -eq 0 && test "${freeb}" -eq 0) || _fail "failed to create ${dtype} dir ino ${inode} datab ${datab} leafb ${leafb} freeb ${freeb}" ;; "leaf") (test "${datab}" -eq 1 && test "${leafb}" -eq 1 && test "${freeb}" -eq 0) || _fail "failed to create ${dtype} dir ino ${inode} datab ${datab} leafb ${leafb} freeb ${freeb}" ;; "node"|"btree") (test "${datab}" -eq 1 && test "${leafb}" -eq 1 && test "${freeb}" -eq 1) || _fail "failed to create ${dtype} dir ino ${inode} datab ${datab} leafb ${leafb} freeb ${freeb}" ;; *) _fail "Unknown directory type ${dtype}" esac } # Check structure of XFS attr __populate_check_xfs_attr() { dev="$1" inode="$2" atype="$3" datab=0 leafb=0 #echo "== check attr ${inode} type ${dtype}" ; $XFS_DB_PROG -x -c "inode ${inode}" -c "bmap -a" "${SCRATCH_DEV}" $XFS_DB_PROG -x -c "inode ${inode}" -c "ablock 0" -c "stack" "${SCRATCH_DEV}" | grep -q 'file attr block is unmapped' || datab=1 $XFS_DB_PROG -x -c "inode ${inode}" -c "ablock 1" -c "stack" "${SCRATCH_DEV}" | grep -q 'file attr block is unmapped' || leafb=1 case "${atype}" in "shortform"|"inline"|"local") (test "${datab}" -eq 0 && test "${leafb}" -eq 0) || _fail "failed to create ${atype} attr ino ${inode} datab ${datab} leafb ${leafb}" ;; "leaf") (test "${datab}" -eq 1 && test "${leafb}" -eq 0) || _fail "failed to create ${atype} attr ino ${inode} datab ${datab} leafb ${leafb}" ;; "node"|"btree") (test "${datab}" -eq 1 && test "${leafb}" -eq 1) || _fail "failed to create ${atype} attr ino ${inode} datab ${datab} leafb ${leafb}" ;; *) _fail "Unknown attribute type ${atype}" esac } # Check that populate created all the types of files we wanted _scratch_xfs_populate_check() { _scratch_mount extents_file="$(__populate_find_inode "${SCRATCH_MNT}/S_IFREG.FMT_EXTENTS")" btree_file="$(__populate_find_inode "${SCRATCH_MNT}/S_IFREG.FMT_BTREE")" inline_dir="$(__populate_find_inode "${SCRATCH_MNT}/S_IFDIR.FMT_INLINE")" block_dir="$(__populate_find_inode "${SCRATCH_MNT}/S_IFDIR.FMT_BLOCK")" leaf_dir="$(__populate_find_inode "${SCRATCH_MNT}/S_IFDIR.FMT_LEAF")" node_dir="$(__populate_find_inode "${SCRATCH_MNT}/S_IFDIR.FMT_NODE")" btree_dir="$(__populate_find_inode "${SCRATCH_MNT}/S_IFDIR.FMT_BTREE")" local_slink="$(__populate_find_inode "${SCRATCH_MNT}/S_IFLNK.FMT_LOCAL")" extents_slink="$(__populate_find_inode "${SCRATCH_MNT}/S_IFLNK.FMT_EXTENTS")" bdev="$(__populate_find_inode "${SCRATCH_MNT}/S_IFBLK")" cdev="$(__populate_find_inode "${SCRATCH_MNT}/S_IFCHR")" local_attr="$(__populate_find_inode "${SCRATCH_MNT}/ATTR.FMT_LOCAL")" leaf_attr="$(__populate_find_inode "${SCRATCH_MNT}/ATTR.FMT_LEAF")" node_attr="$(__populate_find_inode "${SCRATCH_MNT}/ATTR.FMT_NODE")" btree_attr="$(__populate_find_inode "${SCRATCH_MNT}/ATTR.FMT_BTREE")" blksz="$(stat -f -c '%s' "${SCRATCH_MNT}")" dblksz="$(xfs_info "${SCRATCH_MNT}" | grep naming.*bsize | sed -e 's/^.*bsize=//g' -e 's/\([0-9]*\).*$/\1/g')" leaf_lblk="$((32 * 1073741824 / blksz))" node_lblk="$((64 * 1073741824 / blksz))" umount "${SCRATCH_MNT}" __populate_check_xfs_dformat "${SCRATCH_DEV}" "${extents_file}" "extents" __populate_check_xfs_dformat "${SCRATCH_DEV}" "${btree_file}" "btree" __populate_check_xfs_dir "${SCRATCH_DEV}" "${inline_dir}" "inline" __populate_check_xfs_dir "${SCRATCH_DEV}" "${block_dir}" "block" __populate_check_xfs_dir "${SCRATCH_DEV}" "${leaf_dir}" "leaf" __populate_check_xfs_dir "${SCRATCH_DEV}" "${node_dir}" "node" __populate_check_xfs_dir "${SCRATCH_DEV}" "${btree_dir}" "btree" __populate_check_xfs_dformat "${SCRATCH_DEV}" "${btree_dir}" "btree" __populate_check_xfs_dformat "${SCRATCH_DEV}" "${bdev}" "dev" __populate_check_xfs_dformat "${SCRATCH_DEV}" "${cdev}" "dev" __populate_check_xfs_attr "${SCRATCH_DEV}" "${local_attr}" "local" __populate_check_xfs_attr "${SCRATCH_DEV}" "${leaf_attr}" "leaf" __populate_check_xfs_attr "${SCRATCH_DEV}" "${node_attr}" "node" __populate_check_xfs_attr "${SCRATCH_DEV}" "${btree_attr}" "btree" __populate_check_xfs_aformat "${SCRATCH_DEV}" "${btree_attr}" "btree" } # Check data fork format of ext4 file __populate_check_ext4_dformat() { dev="$1" inode="$2" format="$3" extents=0 etree=0 debugfs -R "stat <${inode}>" "${dev}" 2> /dev/null | grep 'ETB[0-9]' -q && etree=1 iflags="$(debugfs -R "stat <${inode}>" "${dev}" 2> /dev/null | grep 'Flags:' | sed -e 's/^.*Flags: \([0-9a-fx]*\).*$/\1/g')" test "$(echo "${iflags}" | awk '{print and(strtonum($1), 0x80000);}')" -gt 0 && extents=1 case "${format}" in "blockmap") test "${extents}" -eq 0 || _fail "failed to create ino ${inode} with blockmap" ;; "extent"|"extents") test "${extents}" -eq 1 || _fail "failed to create ino ${inode} with extents" ;; "etree") (test "${extents}" -eq 1 && test "${etree}" -eq 1) || _fail "failed to create ino ${inode} with extent tree" ;; *) _fail "Unknown dformat ${format}" esac } # Check attr fork format of ext4 file __populate_check_ext4_aformat() { dev="$1" inode="$2" format="$3" ablock=1 debugfs -R "stat <${inode}>" "${dev}" 2> /dev/null | grep 'File ACL: 0' -q && ablock=0 case "${format}" in "local"|"inline") test "${ablock}" -eq 0 || _fail "failed to create inode ${inode} with ${format} xattr" ;; "block") test "${extents}" -eq 1 || _fail "failed to create inode ${inode} with ${format} xattr" ;; *) _fail "Unknown aformat ${format}" esac } # Check structure of ext4 dir __populate_check_ext4_dir() { dev="$1" inode="$2" dtype="$3" htree=0 inline=0 iflags="$(debugfs -R "stat <${inode}>" "${dev}" 2> /dev/null | grep 'Flags:' | sed -e 's/^.*Flags: \([0-9a-fx]*\).*$/\1/g')" test "$(echo "${iflags}" | awk '{print and(strtonum($1), 0x1000);}')" -gt 0 && htree=1 test "$(echo "${iflags}" | awk '{print and(strtonum($1), 0x10000000);}')" -gt 0 && inline=1 case "${dtype}" in "inline") (test "${inline}" -eq 1 && test "${htree}" -eq 0) || _fail "failed to create ${dtype} dir ino ${inode} htree ${htree} inline ${inline}" ;; "block") (test "${inline}" -eq 0 && test "${htree}" -eq 0) || _fail "failed to create ${dtype} dir ino ${inode} htree ${htree} inline ${inline}" ;; "htree") (test "${inline}" -eq 0 && test "${htree}" -eq 1) || _fail "failed to create ${dtype} dir ino ${inode} htree ${htree} inline ${inline}" ;; *) _fail "Unknown directory type ${dtype}" ;; esac } # Check that populate created all the types of files we wanted _scratch_ext4_populate_check() { _scratch_mount extents_file="$(__populate_find_inode "${SCRATCH_MNT}/S_IFREG.FMT_EXTENTS")" etree_file="$(__populate_find_inode "${SCRATCH_MNT}/S_IFREG.FMT_ETREE")" block_dir="$(__populate_find_inode "${SCRATCH_MNT}/S_IFDIR.FMT_BLOCK")" htree_dir="$(__populate_find_inode "${SCRATCH_MNT}/S_IFDIR.FMT_HTREE")" extents_slink="$(__populate_find_inode "${SCRATCH_MNT}/S_IFLNK.FMT_EXTENTS")" local_attr="$(__populate_find_inode "${SCRATCH_MNT}/ATTR.FMT_LOCAL")" block_attr="$(__populate_find_inode "${SCRATCH_MNT}/ATTR.FMT_BLOCK")" umount "${SCRATCH_MNT}" __populate_check_ext4_dformat "${SCRATCH_DEV}" "${extents_file}" "extents" __populate_check_ext4_dformat "${SCRATCH_DEV}" "${etree_file}" "etree" __populate_check_ext4_dir "${SCRATCH_DEV}" "${block_dir}" "block" __populate_check_ext4_dir "${SCRATCH_DEV}" "${htree_dir}" "htree" __populate_check_ext4_dformat "${SCRATCH_DEV}" "${extents_slink}" "extents" __populate_check_ext4_aformat "${SCRATCH_DEV}" "${local_attr}" "local" __populate_check_ext4_aformat "${SCRATCH_DEV}" "${block_attr}" "block" } # Populate a scratch FS and check the contents to make sure we got that _scratch_populate() { case "${FSTYP}" in "xfs") _scratch_xfs_populate _scratch_xfs_populate_check ;; "ext4") _scratch_ext4_populate _scratch_ext4_populate_check ;; *) _fail "Don't know how to populate a ${FSTYP} filesystem." ;; esac } # Modify various files after a fuzzing operation _scratch_fuzz_modify() { nr="$1" test -z "${nr}" && nr=50000 echo "+++ touch ${nr} files" $XFS_IO_PROG -f -c "pwrite -S 0x63 0 ${BLK_SZ}" "/tmp/afile" > /dev/null date="$(date)" find "${SCRATCH_MNT}/" -type f 2> /dev/null | head -n "${nr}" | while read f; do setfattr -n "user.date" -v "${date}" "$f" cat "/tmp/afile" >> "$f" mv "$f" "$f.longer" done rm -rf "/tmp/afile" echo "+++ create files" cp -pRdu "${SRCDIR}" "${SCRATCH_MNT}/test.moo" sync echo "+++ remove files" rm -rf "${SCRATCH_MNT}/test.moo" rm -rf "${SCRATCH_MNT}/test.1" } # Try to access files after fuzzing _scratch_fuzz_test() { echo "+++ ls -laR" >> $seqres.full ls -laR "${SCRATCH_MNT}/test.1/" >/dev/null 2>&1 echo "+++ cat files" >> $seqres.full (find "${SCRATCH_MNT}/test.1/" -type f -size -1048576k -print0 | xargs -0 cat) >/dev/null 2>&1 }