jborean93 / smbprotocol

Python SMBv2 and v3 Client
MIT License
308 stars 72 forks source link

Unable to read files using that are being used by some other users' proccesses on remote server (High-level API) #282

Open paf91 opened 5 days ago

paf91 commented 5 days ago

When trying to read bytes from file using share access flag

with smb_client.open_file(r"\\server\share\file.txt", mode='rb', share_access='r') as fd:
    file_bytes = fd.read()

or without flag

with open_file(r"\\server\share\file.txt", mode="rb") as fd:
    file_bytes = fd.read()

or simply copy file to read it afterwards using smbutils

from smbclient.shutil import copy
copy(r"\\server\share\file.txt", "file.txt")

I'm getting error:

The process cannot access the file because it is being used by another process

Meanwhile I could

But can't access in python programm in linux with the above exception It's nearly impossible to read files that are constantly used and shared by many people

adiroiban commented 5 days ago

I think that the default behaviour in Python is to open a file in exclusive mode... without shared access.

As far as I know, when opening a file using the open() API there is no high level Python API to define the Windows shared access model.

I think that you will need to use the low-level smbprotocol API to open the file in shared mode.

The file open request can look like this. The important part is share_access=ShareAccess.FILE_SHARE_READ

    handler = Open(tree, share_path)
    handler.create(
        impersonation_level=ImpersonationLevel.Impersonation,
        desired_access=(
            FilePipePrinterAccessMask.FILE_READ_DATA
            | FilePipePrinterAccessMask.FILE_READ_ATTRIBUTES
            | FilePipePrinterAccessMask.FILE_READ_EA
            ),
        file_attributes=SMBFileAttributes.FILE_ATTRIBUTE_NORMAL,
        share_access=ShareAccess.FILE_SHARE_READ,
        # Fail if file doesn't exist.
        create_disposition=CreateDisposition.FILE_OPEN,
        create_options=(
            CreateOptions.FILE_NON_DIRECTORY_FILE
            | CreateOptions.FILE_OPEN_REPARSE_POINT
            ),
        )
paf91 commented 5 days ago

@adiroiban I'm having issues with low-level API since shared path contains spaces (or maybe UTF-8 symbols). Meanwhile high-level API works fine.

e.g. share could be like "\server\share dir\test П\"

image
adiroiban commented 5 days ago

With all the obfuscation and just a screenshot, it's hard to troubleshoot this.

Please provide a short but complete example which demonstrates the problem.

paf91 commented 5 days ago

if you mean the full example, then this would be easiest way: To reproduce error, you need to have a share name with spaces and UTF-8 symbols, e.g. \\server\share dir\ПП 123\Д 5\

import logging
import uuid

from smbprotocol.connection import Connection
from smbprotocol.session import Session
from smbprotocol.tree import TreeConnect

log = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG)

server = "testsever"
port = 445
username = "smbuser"
password = "smbpassword"
share = rf"\\{server}\share dir\ПП 123\Д 5\"

connection = Connection(uuid.uuid4(), server, port)
connection.connect()

try:
    session = Session(connection, username, password)
    session.connect()
    tree = TreeConnect(session, share)
    tree.connect()
except Exception as e:
   logger.error(msg=f"Can't connect to SMB share: {e}")
finally:
    connection.disconnect(True)

This step tree = TreeConnect(session, share) gives error: The specified share name cannot be found on the remote server

Meanwhile you could easily do access files/dirs in this path using high-level api

jborean93 commented 19 hours ago

Sorry I didn't reply earlier as I was away on leave. The SMB protocol is typically modelled after the Windows sharing mechanism used on Windows. This means that if a file is opened by another process (locally or also through SMB) then the existing and new handle needs to be opened with the required file share flags. In this case you can use the low level API to open the file handle but luckily the high level API also provides a way to open with custom share flags. The open_file method has a share_access kwarg which is documented at .https://github.com/jborean93/smbprotocol/blob/2ce49ef644e931d5cd426c2feee730012efc057f/src/smbclient/_os.py#L361-L367

In this case if you want to open it with read file share then you can do share_access='r'. The combination you need to use depends on the access you are requesting, what access you will allow other processes to open it as, and what existing processes with an open handle will allow.

Meanwhile you could easily do access files/dirs in this path using high-level api

While you don't need the low level API as open_file has the share_access kwarg, the two APIs support the same types of paths. At a basic level SMB uses UTF-16-LE encoded strings and in Python a normal string should always support encoding to this format. In your case it's probably failing because the TreeConnect is just meant to be \\{server}\{share} whereas you are giving the full path including the directories and file path inside the share.

paf91 commented 10 hours ago

@jborean93 As stated in issue, shared flag doesn't help

with smb_client.open_file(r"\\server\share\file.txt", mode='rb', share_access='r') as fd:
    file_bytes = fd.read()

I believe it's a bug. Meanwhile in library pysmb there is no such issue when i copy file using

        with open('text.txt', 'wb') as fp:
            conn.retrieveFile('sharename', '/somefolder/text.txt', fp)
jborean93 commented 9 hours ago

My apologies I missed that you tried the share_access flag. I will have to look into pysmb and see how it opens the file using your example that works. If pysmb can do it then so should this library (unless it’s some special SMB1 things).

jborean93 commented 9 hours ago

Looks like it also allows write share access so try adding w to the share_access kwarg https://github.com/miketeo/pysmb/blob/a527d5a57dc2c16ee26056c88bace39a7623065b/python3/smb/base.py#L930. Keep in mind there is nothing to stop another process from editing the file as you are copying.