. "$BST_LIB" # BST-TIMEOUT: 900 # ~72 control-driven cases plus ~25 surface/probe cases, and # # each control case costs three execs (one sed to read its # # record, the tool, one sed to strip the plugin line) -- # # ~240 execs, which at the ~1s/exec 99-aoetools.test # # measured on this guest is ~4 minutes. Nothing here waits # # on a network: mount.cifs is driven only through cases # # whose diagnostic is printed by the option parser, and the # # one case that goes on to mount(2) is refused locally. # cifs-utils 7.0 -- the CIFS/SMB mount helper and the Windows ACL userland, on a # kernel with no CIFS filesystem. # # --------------------------------------------------------------------------- # WHAT THIS MACHINE CAN AND CANNOT TEST. # # arch/wasm32/configs/wasm32_defconfig carries "# CONFIG_NETWORK_FILESYSTEMS is # not set", so cifs and smb3 are not registered filesystems and no share can be # mounted. Everything downstream of a mount is therefore unreachable -- but that # is not most of this package. Two halves of it are pure computation and have a # right answer that does not depend on any kernel: # # the ACL codec getcifsacl decodes a binary Windows security descriptor # to text; setcifsacl edits one and re-encodes it. Both are # arithmetic over a byte buffer. # the option parser mount.cifs canonicalises an -o string, and rejects a # malformed one, long before it calls mount(2). # # Those are what is asserted here, byte for byte against a native x86-64 build # of the same SRCREV with the same patches (data/cifs-utils-native.txt, from # src/cifs_utils_mknative.sh). A line that differs is a statement about wasm32. # # --------------------------------------------------------------------------- # THE CODEC IS DRIVEN THROUGH A HARNESS, AND WHY THAT IS NOT A SIMULATION. # # getcifsacl and setcifsacl have exactly one input path each and it is an # extended attribute: getxattr(2) of system.cifs_ntsd_full / system.cifs_acl # (getcifsacl.c:442) and setxattr(2) of the same (setcifsacl.c:1520). Upstream # expects the kernel's CIFS client to produce that attribute on a mounted share. # On EVERY Linux, not just this one, system.cifs_acl is answered by the CIFS # client and by nothing else -- measured on an ext4 build host, where user.* is # accepted and system.cifs_acl returns EOPNOTSUPP from the same process. So the # shipped binaries' input is closed here, and no amount of local staging opens # it. # # ${LIBEXEC}/cifs-utils-tests/{get,set}cifsacl-blob answer those two calls from a # plain file instead. They are getcifsacl.c and setcifsacl.c compiled # UNMODIFIED, in the package's own do_compile, by the same compiler, from the # same sources as /usr/bin/getcifsacl -- -Dgetxattr/-Dsetxattr rename the call # and the declaration and nothing else (recipes-support/cifs). # parse_sec_desc, parse_acl, print_ace, print_sid and setcifsacl's ACE builders # are the shipped code. The native control is built the same way, so the harness # cancels and what is compared is the codec. # # AND THE SHIPPED BINARY IS STILL TRIED. The reachability of system.cifs_acl is # PROBED at run time, on the directory this suite actually works in, and the # shipped-binary case below is written against the live answer -- so the day an # image grows the attribute, that case starts asserting instead of skipping, on # its own evidence rather than on this comment. # # THE PROBE'S CONTROLS ARE THE POINT OF IT. "system.cifs_acl was refused" is a # statement about the filesystem only while this process can create a file and # set a user.* attribute on it. The probe reports all three from one run, and # the skip is gated on the first; if plain writes stop working, this suite reds # rather than quietly skipping. # # --------------------------------------------------------------------------- # THE CONTROL FILE DRIVES THE CASE SET, NOT JUST THE EXPECTATIONS. # # Every case below is read out of data/cifs-utils-native.txt: its SPEC line says # what to run, its CASE line the expected exit status, its LINE lines the # expected output. Nothing here restates a command the oracle ran, so the two # cannot drift -- a case added to src/cifs_utils_mknative.sh appears here on the # next regeneration with no edit to this file. bst_ok cifs-utils/suite-canary # first line: absorbs brintos/linux#96, which drops it require getcifsacl cifs-utils/present LX=/usr/libexec/cifs-utils-tests CTL=$(bst_local "$BST_DATA/cifs-utils-native.txt") W=${TMPDIR:-/tmp}/cifs-utils.$$ NL=' ' mkdir -p "$W/mnt" # --------------------------------------------------------------------------- # 1. What the port ships. # --------------------------------------------------------------------------- # # Checked by name and by location, and not for tidiness. automake installs these # from three separate lists (root_sbin_PROGRAMS, bin_PROGRAMS, sbin_PROGRAMS) # and upstream's own do_install:append rmdir's ${bindir} and ${sbindir} on a # non-usrmerge distro -- harmless only while they are non-empty. A PACKAGECONFIG # that silently failed to take would empty one of them and the package would # ship mount.cifs alone. check cifs-utils/tool-mount-cifs test -x /sbin/mount.cifs check cifs-utils/tool-mount-smb3 test -L /sbin/mount.smb3 check cifs-utils/tool-cifscreds test -x /usr/bin/cifscreds check cifs-utils/tool-getcifsacl test -x /usr/bin/getcifsacl check cifs-utils/tool-setcifsacl test -x /usr/bin/setcifsacl check cifs-utils/tool-cifs-idmap test -x /usr/sbin/cifs.idmap check cifs-utils/harness-get test -x "$LX/getcifsacl-blob" check cifs-utils/harness-set test -x "$LX/setcifsacl-blob" check cifs-utils/probe-present test -x "$LX/cifs-utils-probe" # idmapwb.so must be ABSENT. It is the winbind idmap plugin, built -shared -fpic, # which this arm's linker cannot produce -- so its presence would mean the port's # CONFIG_PLUGIN gate did not take and the image got there some other way. if [ -e /usr/lib/cifs-utils/idmapwb.so ]; then bst_notok cifs-utils/no-idmapwb "/usr/lib/cifs-utils/idmapwb.so exists -- a -shared -fpic object cannot be produced on this arm, so the CONFIG_PLUGIN gate in 0001-build-the-ACL-and-idmap-tools-without-libwbclient.patch did not take" else bst_ok cifs-utils/no-idmapwb fi if [ ! -s "$CTL" ]; then bst_notok cifs-utils/control-present "native control $CTL missing or empty -- every codec and parser case below is unscorable" return 0 2>/dev/null || exit 0 fi bst_ok cifs-utils/control-present CTLVER=$(sed -n 's|^VERSION ||p' "$CTL") LONG=$(sed -n 's|^CONST LONG ||p' "$CTL") # The control speaks for ONE version. Assert that the shipped tools, the harness # and the oracle all agree on it, or every comparison below is between two # different programs that happen to have the same name. check_out cifs-utils/version-mount "mount.cifs version: $CTLVER" /sbin/mount.cifs -V check_out cifs-utils/version-get "Version: $CTLVER" /usr/bin/getcifsacl -v check_out cifs-utils/version-set "Version: $CTLVER" /usr/bin/setcifsacl -v check_out cifs-utils/version-blobget "Version: $CTLVER" "$LX/getcifsacl-blob" -v check_out cifs-utils/version-blobset "Version: $CTLVER" "$LX/setcifsacl-blob" -v # The fixtures, copied out of the read-only rootfs because setcifsacl WRITES to # the file it is given. A missing fixture set would make every decode case red # for one reason, so it is named once here instead. cp "$BST_DATA"/cifs-utils-*.sd "$W/" 2>/dev/null nfix=$(ls "$W"/cifs-utils-*.sd 2>/dev/null | wc -l) if [ "$nfix" -ge 16 ]; then bst_ok cifs-utils/fixtures-staged else bst_notok cifs-utils/fixtures-staged "only $nfix of the 16 reference descriptors reached $W from $BST_DATA" fi # --------------------------------------------------------------------------- # 2. The environment probe: is the codec's own input path open here? # --------------------------------------------------------------------------- probe=$("$LX/cifs-utils-probe" "$W" 2>&1) case "$probe" in *"PROBE end"*) bst_ok cifs-utils/probe-ran ;; *) bst_notok cifs-utils/probe-ran "the probe did not reach its end marker: $(_bst_1line "$probe")" ;; esac p_write=$(printf '%s\n' "$probe" | sed -n "s|^WRITE $W ||p") p_user=$(printf '%s\n' "$probe" | sed -n "s|^USER $W ||p") p_sys=$(printf '%s\n' "$probe" | sed -n "s|^SYSTEM $W ||p") p_fs=$(printf '%s\n' "$probe" | sed -n "s|^FSTYPE $W ||p") p_cifs=$(printf '%s\n' "$probe" | sed -n 's|^FSREG cifs ||p') p_smb3=$(printf '%s\n' "$probe" | sed -n 's|^FSREG smb3 ||p') m_share=$(printf '%s\n' "$probe" | sed -n 's|^MMAP shared writeback=||p') m_priv=$(printf '%s\n' "$probe" | sed -n 's|^MMAP private writeback=||p') m_pre=$(printf '%s\n' "$probe" | sed -n 's|^MMAP shared pre=\([a-z]*\) .*|\1|p') # The control for every attribution below. if [ "$p_write" = ok ]; then bst_ok cifs-utils/probe-control-write else bst_notok cifs-utils/probe-control-write "the probe could not create and write a plain file in $W (fstype $p_fs): '$p_write'. Nothing in this suite may be attributed to an xattr namespace or to a kernel gap while that is true" fi # Reported as its own case so the day xattrs arrive is visible in the record # rather than only in a skip's detail. user.* and system.* are separate facts # about a future image and own different halves of this suite. if [ "$p_user" = ok ]; then bst_ok cifs-utils/xattr-user-namespace else bst_skip cifs-utils/xattr-user-namespace "setxattr(user.brintos.probe) on $W (fstype $p_fs) is refused: '$p_user'. This is not a cifs-utils fact -- no tool in this package touches the user.* namespace -- it is recorded because it is what separates 'this filesystem has no xattrs at all' from 'the system.* namespace is refused', and brintos/linux#27 tracks the wasm32 xattr syscalls" fi # --------------------------------------------------------------------------- # 3. The shipped binaries, on the one input path they have. # --------------------------------------------------------------------------- if [ "$p_write" != ok ]; then bst_notok cifs-utils/shipped-getcifsacl "see cifs-utils/probe-control-write -- the probe cannot write a plain file, so no verdict about xattrs is available" elif [ "$p_sys" = ok ]; then # The attribute IS settable here. Drive /usr/bin/getcifsacl for real and hold # it to the same control the harness is held to, modulo the filename line. bst_ok cifs-utils/xattr-system-namespace if "$LX/cifs-utils-probe" --stage "$W/shipped.sd" "$W/cifs-utils-basic.sd" 2>"$W/stage.err"; then want=$(sed -n 's|^LINE decode/basic ||p' "$CTL" | sed 's|^# filename: .*|# filename: shipped.sd|') got=$( (cd "$W" && /usr/bin/getcifsacl shipped.sd) 2>&1 \ | sed -e '/^WARNING: unable to initialize idmapping plugin/d' -e '/^$/d') check_eq cifs-utils/shipped-getcifsacl "$want" "$got" else bst_notok cifs-utils/shipped-getcifsacl "the probe reported system.cifs_acl settable on $W but --stage then failed: $(_bst_1line "$(cat "$W/stage.err" 2>/dev/null)")" fi else bst_skip cifs-utils/xattr-system-namespace "setxattr(system.cifs_acl) on $W (fstype $p_fs) is refused: '$p_sys', while plain writes succeed and user.* reports '$p_user'" bst_skip cifs-utils/shipped-getcifsacl "/usr/bin/getcifsacl reads its descriptor ONLY from the system.cifs_acl xattr, which on every Linux is answered by the mounted CIFS client and by no other filesystem (measured identically on an ext4 build host: user.* accepted, system.cifs_acl EOPNOTSUPP). With no CIFS filesystem in this kernel there is no mount that could carry it, so the shipped binary has no reachable input and the codec is asserted through the blob harness below instead" fi # --------------------------------------------------------------------------- # 3a. mount.cifs's privilege separation: does the parse survive the fork? # --------------------------------------------------------------------------- # # mount.cifs does not parse its command line in the process that mounts. It # mmaps a struct parsed_mount_info MAP_ANONYMOUS|MAP_SHARED, forks, has the child # drop privileges and fill it in, waits, and reads the result back out of that # mapping (mount.cifs.c:2087, :2181, :2213). The resolved address, the host, the # share and the canonical option string all cross the fork that way and by no # other route -- so this is not a detail of the tool, it is whether the tool can # work. # # The controls decide what the answer means: `pre` is the parent's write read # back by the child (fails only if the mapping or the fork is broken outright), # and `priv` is the identical experiment on MAP_PRIVATE, which must report # invisible. Two arms saying the same thing would be a fact about the probe. if [ "$m_pre" != ok ] || [ "$m_priv" != invisible ]; then bst_notok cifs-utils/mmap-shared-across-fork "the probe's own controls did not hold (pre=$m_pre private-writeback=$m_priv), so nothing here may be attributed to MAP_SHARED semantics" elif [ "$m_share" = visible ]; then bst_ok cifs-utils/mmap-shared-across-fork else bst_notok cifs-utils/mmap-shared-across-fork "a child's write to an anonymous MAP_SHARED mapping is $m_share to the parent while the MAP_PRIVATE control is correctly invisible and the pre-fork read succeeds -- brintos/linux#83. mount.cifs passes its ENTIRE parse result back across the fork through exactly such a mapping, so on this kernel the mounting parent reads an empty host, share, address and option string however well the child parsed" fi # --------------------------------------------------------------------------- # 4. The kernel gap this package sits on, named once and measured. # --------------------------------------------------------------------------- if [ "$p_cifs" = present ] || [ "$p_smb3" = present ]; then bst_ok cifs-utils/kernel-cifs-filesystem else bst_notok cifs-utils/kernel-cifs-filesystem "/proc/filesystems registers neither cifs nor smb3 (cifs=$p_cifs smb3=$p_smb3): arch/wasm32/configs/wasm32_defconfig is built '# CONFIG_NETWORK_FILESYSTEMS is not set', so mount.cifs has no filesystem to mount and getcifsacl/setcifsacl have no mount that could carry system.cifs_acl. This is a kernel-config gap, not a cifs-utils defect -- brintos/linux#108" fi # --------------------------------------------------------------------------- # 5. The codec and the option parser, driven entirely from the control. # --------------------------------------------------------------------------- # # run_spec : run one case, leave the tool's exit status in RC and # its noise-stripped output in OUT. # # The output goes through a FILE rather than a pipeline, because `out=$(cmd | # filter); rc=$?` reads the FILTER's status -- which is a sed and therefore 0 # whatever the tool did, so every rc compared below would have been 0 by # construction, including the diagnostics whose whole subject is a non-zero exit. # # The line that is stripped is the idmap plugin's dlopen failure. Its text is # dlerror()'s and differs between a glibc host and a static wasm32 target for # reasons that have nothing to do with the codec; that the tools DEGRADE # correctly when no plugin loads (raw SID text, which is what every expectation # below carries) is asserted by the comparison itself. run_case() { "$@" >"$W/o" 2>&1 RC=$? OUT=$(sed -e '/^WARNING: unable to initialize idmapping plugin/d' -e '/^$/d' "$W/o") return 0 } for c in $(sed -n 's|^SPEC \([^ ]*\) .*|\1|p' "$CTL"); do # One sed for the whole record: the SPEC line first, then rc=, then the # expected output. The trailing space in each pattern is load-bearing -- # without it `edit/add` also matches `edit/add-reorder`. rec=$(sed -n -e "s|^SPEC $c |SPEC |p" -e "s|^CASE $c rc=|rc=|p" -e "s|^LINE $c ||p" "$CTL") spec=${rec%%"$NL"*} want=${rec#*"$NL"} spec=${spec#SPEC } verb=${spec%% *} args=${spec#"$verb"} args=${args# } RC=0; OUT="" case $verb in get) run_case sh -c "cd '$W' && exec '$LX/getcifsacl-blob' $args" ;; getr) run_case sh -c "cd '$W' && exec '$LX/getcifsacl-blob' -r $args" ;; edit) fix=${args%% *}; eargs=${args#"$fix"}; eargs=${eargs# } name=${c#edit/} cp "$W/cifs-utils-$fix.sd" "$W/edit-$name.sd" run_case sh -c "cd '$W' && exec '$LX/setcifsacl-blob' $eargs 'edit-$name.sd'" ;; rt) # Add an ACE and delete the same ACE: the decoded text must come back to # what it was. The one case here that scores the encoder against the # decoder rather than against a stored expectation. fix=${args%% *}; ace=${args#* } cp "$W/cifs-utils-$fix.sd" "$W/rt-$fix.sd" (cd "$W" && "$LX/setcifsacl-blob" -a "$ace" "rt-$fix.sd" >/dev/null 2>&1) (cd "$W" && "$LX/setcifsacl-blob" -D "$ace" "rt-$fix.sd" >/dev/null 2>&1) run_case sh -c "cd '$W' && exec '$LX/getcifsacl-blob' 'rt-$fix.sd'" OUT=$(printf '%s\n' "$OUT" | sed "s|^# filename: rt-$fix.sd\$|# filename: cifs-utils-$fix.sd|") ;; mount|mountv) margs=$(printf '%s' "$args" | sed -e "s|MNT|$W/mnt|g" -e "s|LONG|$LONG|g") run_case sh -c "cd '$W/mnt' && exec /sbin/mount.cifs $margs