jborean93 / smbprotocol

Python SMBv2 and v3 Client
MIT License
318 stars 73 forks source link

STATUS_NOT_SUPPORTED: 0xc00000bb exception in smbclient.copyfile with NetApp Ontap v2.1+ #178

Closed stvacs closed 2 years ago

stvacs commented 2 years ago

Hi, I am using a simple program (see attached file) to test server-side copy with a NetApp Ontap server (v2.1 or higher). This program runs fine with other server as target eg. a Synology Samba server. But with NetApp following error is thrown:

Traceback (most recent call last): File "y:\pysmb-test\minimal_example.py", line 23, in smbclient.copyfile( sf_name, tf_name ) File "C:\Users\user\pyrt\lib\site-packages\smbclient_os.py", line 158, in copyfile with SMBFileTransaction(src_fd) as transaction_src: File "C:\Users\user\pyrt\lib\site-packages\smbclient_io.py", line 277, in exit self.commit() File "C:\Users\user\pyrt\lib\site-packages\smbclient_io.py", line 364, in commit raise failures[0] smbprotocol.exceptions.SMBOSError: [Error 0] [NtStatus 0xc00000bb] Unknown NtStatus error returned 'STATUS_NOT_SUPPORTED'

To my knowledge both server-side copy (e.g. FSCTL_SRV_COPYCHUNK support) and ODX are enabled for the CIFS share in question. I have attached the debug log from the test program.

Is there anything I can do on the client side to make this work?

Thanks for helping.

_smb.log minimal_example.py.txt

jborean93 commented 2 years ago

At a guess the NetApp server might not support transacted operations that smbclient is trying to do. Looking at the code it looks like the following operations are done

The logs you sent unfortunately seem to include this operation in it so I can't tell with of the 2 failed. This isn't your fault, just a gap in the logging unfortunately.

My guess is the FSCTL_SRV_REQUEST_RESUME_KEY is not implemented on the Netapp side so let's try and test out that assumption. Can you try to run the following:

import smbclient

from smbprotocol.ioctl import CtlCode, IOCTLFlags, SMB2IOCTLRequest, SMB2IOCTLResponse, SMB2SrvRequestResumeKey

path = r'\\server\share\test.txt'
with smbclient.open_file(path, mode='rb', share_access='r', buffering=0) as fd:
    ioctl_req = SMB2IOCTLRequest()
    ioctl_req['ctl_code'] = CtlCode.FSCTL_SRV_REQUEST_RESUME_KEY
    ioctl_req['file_id'] = fd.fd.file_id
    ioctl_req['max_output_response'] = 32
    ioctl_req['flags'] = IOCTLFlags.SMB2_0_IOCTL_IS_FSCTL
    ioctl_req['buffer'] = b""

    sid = fd.fd.tree_connect.session.session_id
    tid = fd.fd.tree_connect.tree_connect_id
    req = fd.fd.connection.send(ioctl_req, sid, tid)

    header_resp = fd.fd.connection.receive(req)
    resp = SMB2IOCTLResponse()
    resp.unpack(header_resp['data'].get_value())

    resume_key = SMB2SrvRequestResumeKey()
    resume_key.unpack(resp['buffer'].get_value())

    print(header_resp)
    print(resp)
    print(resume_key)

This should print out the header response containing STATUS_SUCCESS and the IOCTL response and the resume key information. If it fails with STATUS_NOT_SUPPORTED we know this is the part that's failing and will have to potentially find some alternatives.

stvacs commented 2 years ago

Thank you for your suggestion and some additional test code. Below you find the result. The exception is raised on execution of "header_resp = fd.fd.connection.receive(req)", e.g. all code below is never excuted. This indicates that FSCTL_SRV_REQUEST_RESUME_KEY is not implemented.

Is it possible to implement FSCTL_SRV_COPYCHUNK without FSCTL_SRV_REQUEST_RESUME_KEY request? Based on https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb/c82ff91d-28d8-4274-a2ab-c2f5ade26b98 I had the impression, that FSCTL_SRV_REQUEST_RESUME_KEY was mandatory.

Traceback (most recent call last): File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.496.0_x64qbz5n2kfra8p0\lib\runpy.py", line 196, in _run_module_as_main return _run_code(code, main_globals, None, File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.496.0_x64__qbz5n2kfra8p0\lib\runpy.py", line 86, in _run_code exec(code, run_globals) File "c:\Users\user.vscode\extensions\ms-python.python-2022.6.2\pythonFiles\lib\python\debugpy__main.py", line 45, in cli.main() File "c:\Users\user.vscode\extensions\ms-python.python-2022.6.2\pythonFiles\lib\python\debugpy/..\debugpy\server\cli.py", line 444, in main run() File "c:\Users\user.vscode\extensions\ms-python.python-2022.6.2\pythonFiles\lib\python\debugpy/..\debugpy\server\cli.py", line 285, in run_file runpy.run_path(target_as_str, run_name=compat.force_str("main")) File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.496.0_x64qbz5n2kfra8p0\lib\runpy.py", line 269, in run_path return _run_module_code(code, init_globals, run_name, File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.496.0_x64__qbz5n2kfra8p0\lib\runpy.py", line 96, in _run_module_code _run_code(code, mod_globals, init_globals, File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.496.0_x64qbz5n2kfra8p0\lib\runpy.py", line 86, in _run_code exec(code, run_globals) File "y:\pysmb-test\log_error.py", line 22, in header_resp = fd.fd.connection.receive(req) File "c:\Users\user\pyrt\lib\site-packages\smbprotocol\connection.py", line 1006, in receive raise SMBResponseException(response) smbprotocol.exceptions.NotSupported: Received unexpected status from the server: The request is not supported. (3221225659) STATUS_NOT_SUPPORTED: 0xc00000bb

jborean93 commented 2 years ago

It seems to be a required IOCTL command as the docs for FSCTL_SRV_COPYCHUNK mention that the SourceFile is set to

A key that represents the source file with the data to be copied. This key is obtained through FSCTL_SRV_REQUEST_RESUME_KEY.

In your case if server side copies are not supported you will have to use shutil.copyfileobj to copy files. Theoretically it should be possible to do smbclient.shutil.copyfile and force it to use the more inefficient copy by reading and writing manually but unfortunately the code doesn't have a fallback written and will always use smbclient.copyfile if it shares the same root.

import smbclient
import shutil

with smbclient.open_file(src, mode='rc') as src_fd, smbclient.open_file(dst, mode='wb') as dst_fd:
    shutil.copyfileobj(src_fd, dst_fd)
jborean93 commented 2 years ago

Closing as per the above, without supporting the required IOCTL commands the raw server side copy operation cannot be used. The workaround is to use shutil.copyfileobj with an opened handle on both sides. This is unfortunately not as efficient but the only way around this problem.