Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Dokan wrapper to 1.0.0 #258

Merged
merged 13 commits into from
Jan 22, 2017
Merged

Update Dokan wrapper to 1.0.0 #258

merged 13 commits into from
Jan 22, 2017

Conversation

Liryna
Copy link
Contributor

@Liryna Liryna commented Jul 30, 2016

No description provided.

@Liryna
Copy link
Contributor Author

Liryna commented Jul 30, 2016

@arekbulski I updated regarding your comments.
I have found only 2 print, is there still remaining ?

DOKAN_OPTION_REMOVABLE = 32
# Use removable drive
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two different options to "Use removable drive" ?

@Liryna
Copy link
Contributor Author

Liryna commented Aug 1, 2016

Thank you for your feedback @lurch I fixed them

def Cleanup(self, path, info):
path = path.replace('\\', '/')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this (and all the other places where you've added this) is correct? Paths in pyfilesystem are defined to always (only) be separated by forward-slashes http://docs.pyfilesystem.org/en/latest/concepts.html#paths
Backslashes in pyfilesystem paths should always be preserved, as they're valid filename characters e.g. on Linux.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #256 (comment)
I found no other wait to make it work.
Dokan is only Windows FS.

@lurch
Copy link
Contributor

lurch commented Aug 1, 2016

I'll try to find time to test this on my Windows7 laptop one evening this week...

@@ -5,19 +5,18 @@
Expose an FS object to the native filesystem via Dokan.

This module provides the necessary interfaces to mount an FS object into
the local filesystem using Dokan on win32::
the local filesystem using Dokan on win32:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this double-colon is actually needed? See http://docs.pyfilesystem.org/en/latest/expose/dokan.html

Copy link
Contributor Author

@Liryna Liryna Aug 1, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that's whats for 😄 ! I revert it

@Liryna
Copy link
Contributor Author

Liryna commented Aug 1, 2016

👍
You will simply need to download and install last Dokan RC
https://github.com/dokan-dev/dokany/releases/download/v1.0.0-RC4/DokanSetup_redist.exe
If it does not found dokan1.dll, you can find it in system32 or syswow64 depending of python version used.

@arekbulski
Copy link

arekbulski commented Aug 1, 2016

@Liryna I would like you to look at #241 and #242 and review these two. Either incorporate those into yours or advise to reject them. I will review myself a bit later but this is outside my area.

@Liryna
Copy link
Contributor Author

Liryna commented Aug 1, 2016

Thats where a part of my changes comes :)

def FindStreams(self, path, callback, info):
return STATUS_NOT_IMPLEMENTED

def _dokanpath2pyfs(self, path):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation (tabs vs. spaces).

@Liryna
Copy link
Contributor Author

Liryna commented Aug 1, 2016

@lurch fixed 👍

@@ -806,18 +838,22 @@ def _datetime2timestamp(dtime):

DATETIME_LOCAL_TO_UTC = _datetime2timestamp(datetime.datetime.utcnow()) - _datetime2timestamp(datetime.datetime.now())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this could be removed?

@Liryna
Copy link
Contributor Author

Liryna commented Aug 1, 2016

Done, I hope this will be the last change.

@arekbulski
Copy link

There is a saying among game developers: "Nothing is e-v-e-r done." ^^

>>> mp.path
'Q:\\'
>>> mp.unmount()
>>> fs = MemoryFS()
>>> # Mount in a exisiting empty folder
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo - should be "an existing" (extra n, one less i).
Did you want to add spaces after the commas in your mount examples?

PDOKAN_FILE_INFO)),
("Unmount", CFUNCTYPE(c_int,
PDOKAN_FILE_INFO)),
("ZwCreateFile", CFUNCTYPE(NTSTATUS,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Callbacks should be WINFUNCTYPE instead of CFUNCTYPE to work with new Dokan versions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between WINFUNCTYPE and CFUNCTYPE ?
I had no issue running with CFUNCTYPE

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's for different calling conventions: stdcall (aka wincall) and cdecl respectively.Probably, you've been testing with 64-bit Python. For x64 programs MSVC ignores calling convention declarations and always uses fastcall, so stdcall and cdecl work identically. On x86, though, the Python process just crashes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok @happy-monk ! you are right only tested on 64-bit !
I made the changes et rebase the pull request 👍

# If no access rights are requestsed, only basic metadata is queried.
if not access:
if self.fs.isdir(path):
info.contents.IsDirectory = True
elif not self.fs.exists(path):
raise ResourceNotFoundError(path)
return STATUS_OBJECT_NAME_NOT_FOUND
return

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that there should be some more logic in case of opening directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what kind of logic have you in head ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like this:

if self.fs.isdir(path):
    info.contents.IsDirectory = True
    retun STATUS_SUCCESS

It's weird to try to open directory as file and it generates many exception messages in DebugFS logs.

By the way, I could not create directory through this driver. It created files instead.

Copy link
Contributor Author

@Liryna Liryna Sep 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right it has to return STATUS_SUCCESS.
CreateFile for open directory is normal, it is how windows work 😢

I will try to reproduce the issue you faced. Normally there should be a request with info.contents.IsDirectory == true and a create flag.
I don't have my env with me so it can take some times.

Thank for taking a look at this Pr !

PULONGLONG, # TotalNumberOfFreeBytes
PDOKAN_FILE_INFO)),
("GetVolumeInformation", WINFUNCTYPE(NTSTATUS,
LPWSTR, # VolumeNameBuffer

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently, when invoking Python callbacks, ctypes converts all string pointers in parameters to plain Python strings, which will not do for out-parameters. The easiest way to solve this is replace LPWSTR with PVOID.

This is important, because ctypes.memove (which is used in GetVolumeInformation callback) works with Python strings without exceptions, but can crash the whole process.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that mean I should just change the LPWSTR to PVOID for GetVolumeInformation ?
👍 you seems to know much more than me on this ! Good to have some analyse on the PR !
I made the changes, correct me if I did it wrong

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Now it works without crashes.


@handle_fs_errors
def GetFileSecurity(self, path, securityinformation, securitydescriptor, securitydescriptorlength, neededlength, info):
return STATUS_NOT_IMPLEMENTED

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

STATUS_NOT_IMPLEMENTED is not declared, which leads to unhandled exceptions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flag added 👍

@@ -709,17 +728,18 @@ def GetVolumeInformation(self, vnmBuf, vnmSz, sNum, maxLen, flags, fnmBuf, fnmSz
maxLen[0] = 255
if flags:
flags[0] = 0
Copy link

@happy-monk happy-monk Sep 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The flags probably should be at least FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES | FILE_UNICODE_ON_DISK.

Copy link
Contributor Author

@Liryna Liryna Sep 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this has been added ! thank you !

@Liryna
Copy link
Contributor Author

Liryna commented Sep 23, 2016

@happy-monk Thank you for testing it !
Just to resume the situation, you have issue to create folder for now ? but the device mount without issue, you can read/write a file ?

I will take a look at the CreateFile.

@lurch
Copy link
Contributor

lurch commented Sep 23, 2016

Ditto, thanks for looking at this @happy-monk 👍
(makes me glad I didn't prematurely merge it! ;) )

@happy-monk
Copy link

@Liryna Yes and yes.
@lurch 🤘

@jeffswt
Copy link

jeffswt commented Sep 25, 2016

@Liryna There seems to be some major problems in your commit.

First problem: Your filesystem tends to work well on reading and writing, but not creating folders. Any attempt to create a folder instead creates a file with the same name at the target folder. This makes files' hierarchical manipulations impossible. This bug occurs on all filesystems. Screenshot as follows:

Bug_1

Second problem: The dokan wrapper literally removes files, albeit correctly in the filesystem, but it seems to raise exceptions on the way deleting the files. I do not quite understand the specific implementations, but the debugging messages are indeed irritating. This bug occurs on all filesystems. Debugging message as follows:

Traceback (most recent call last):
  File "_ctypes/callbacks.c", line 234, in 'calling callback function'
  File "C:\Programs\Python3\lib\site-packages\fs\expose\dokan\__init__.py", line 291, in wrapper
    return func(self, *args)
  File "C:\Programs\Python3\lib\site-packages\fs\expose\dokan\__init__.py", line 206, in wrapper
    res = func(*args, **kwds)
  File "C:\Programs\Python3\lib\site-packages\fs\errors.py", line 219, in wrapper
    return func(*args,**kwds)
  File "C:\Programs\Python3\lib\site-packages\fs\expose\dokan\__init__.py", line 492, in Cleanup
    self._pending_delete.remove(path)
KeyError: '/New folder'

Third problem: Minor, but sometimes effects overall experience, that drives may not be unmounted after quite a long time. I haven't found out the reason, but the code and the debugging traceback is here.

>>> from fs.osfs import OSFS
>>> from fs.expose import dokan
>>> f = OSFS('D:\\Desktop')
>>> mp = dokan.mount(f, 'G:\\')
>>> mp.unmount()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Programs\Python3\lib\site-packages\fs\expose\dokan\__init__.py", line 1046, in unmount
    raise OSError("the filesystem could not be unmounted: %s" %(self.path,))
OSError: the filesystem could not be unmounted: G:\
>>> dokan.unmount('G:\\')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Programs\Python3\lib\site-packages\fs\expose\dokan\__init__.py", line 1002, in unmount
    raise OSError("filesystem could not be unmounted: %s" % (path,))
OSError: filesystem could not be unmounted: G:\
>>>
^C
C:\Users\Administrator>python
Python 3.5.1 (v3.5.1:37a07cee5969, Dec  6 2015, 01:38:48) [MSC v.1900 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from fs.expose import dokan
>>> dokan.unmount('G:\\')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Programs\Python3\lib\site-packages\fs\expose\dokan\__init__.py", line 1002, in unmount
    raise OSError("filesystem could not be unmounted: %s" % (path,))
OSError: filesystem could not be unmounted: G:\
>>> 

Fourth problem: ZipFS could not be exposed to dokan. I am not sure whether this is a problem, but this could be related to implementation methods which are not yet implemented. Debugging methods as follows:

Python 3.5.1 (v3.5.1:37a07cee5969, Dec  6 2015, 01:38:48) [MSC v.1900 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from fs.zipfs import ZipFS
>>> from fs.expose import dokan
>>> f = ZipFS('D:\\minetest-0.4.14-win32.zip')
>>> f.listdir()
['minetest-0.4.14']
>>> mp = dokan.mount(f, 'H:\\')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Programs\Python3\lib\site-packages\fs\expose\dokan\__init__.py", line 981, in mount
    mp = MountProcess(fs, path, kwds)
  File "C:\Programs\Python3\lib\site-packages\fs\expose\dokan\__init__.py", line 1039, in __init__
    cmd = cmd % (repr(pickle.dumps((fs, path, dokan_opts, nowait), -1)),)
TypeError: cannot serialize '_io.BufferedReader' object
>>>

Would be grateful if you could solve these problems.

@Liryna
Copy link
Contributor Author

Liryna commented Sep 25, 2016

@ht35268 I will fix the create folder issue but other reported problem that you point does not come from my commit and I don't have the knowledge to fix them.

Feel free to create a pull request with the fix 👍

@lurch
Copy link
Contributor

lurch commented Sep 25, 2016

Fourth problem: ZipFS could not be exposed to dokan.

I expect this might be a problem with ZipFS, rather than Dokan. (ISTR something similar being mentioned before a long time ago)
IIRC the 'fix' is to use the foreground=True option in the dokan.mount call (as this then means the filesystem being exposed (ZipFS in this case) doesn't need to be pickled).

@Liryna
Copy link
Contributor Author

Liryna commented Oct 24, 2016

@ChiBill There was a missing for python 2
I added it in the branch, you can pull and run it.

I made my last test on python 3 & 2. everything work great 👍

# All rights reserved; available under the terms of the MIT License.
"""

fs.expose.dokan.libdokan: low-level ctypes interface to Dokan

"""

import ctypes
from ctypes import *
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You really shouldn't be using both these import lines.
IMHO you should just pick one or the other, and then modify the rest of the code as necessary (e.g. remove the first import, and then change ctypes.POINTER to just POINTER etc.).

@happy-monk
Copy link

@Liryna I think, it would be better if you did this the other way around. libdokan heavily uses objects from ctypes, so it's reasonable to user import * there. dokan.__init__ uses them only occasionally, so it's better to use import ctypes there to not pollute the namespace with unnecessary names.

@Liryna
Copy link
Contributor Author

Liryna commented Oct 24, 2016

Good for me so 👍 bc2e17c

@jeffswt
Copy link

jeffswt commented Oct 24, 2016

@happy-monk Good idea. Though personally I prefer not using from ... import * as it ruins the namespaces.

@happy-monk
Copy link

@ht35268 I think, the main purpose of libdokan as the separate module is to isolate all ctypes stuff.
@Liryna Good, but your two last commits pretty much cancel each other. So, maybe, you should squash them together? :) (If you did not plan to squash the whole PR at some point).

@Liryna
Copy link
Contributor Author

Liryna commented Oct 24, 2016

@happy-monk I am going to rebase all the PR when @lurch say the GO 👍

@wgaylord
Copy link

wgaylord commented Nov 1, 2016

Whats the status on this? Is it basically just waiting on a rebase and then it can me pulled?

@Liryna
Copy link
Contributor Author

Liryna commented Nov 1, 2016

Yes @ChiBill. We wait @lurch approval, I rebase and merge it.
Otherwise this has been tested by a couple of people and no issue has been found so far 👍

@wgaylord
Copy link

wgaylord commented Nov 1, 2016

I have been using it from your repo and have found no errors that is why I am asking. (Want to be able to just install it normally.)

@Liryna
Copy link
Contributor Author

Liryna commented Nov 1, 2016

Oh Ok ! Thats a good news 😄 !

res = func(*args,**kwds)
except OSError, e:
res = func(*args, **kwds)
except OSError as e:
if e.errno:
res = -1 * _errno2syserrcode(e.errno)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it right to multiply by -1 here. I think, it scrambles error codes. At least, this may work not the same way in Python as in C/C++.

I tried to open file, which raised PermissionDeniedError in Python, but I did not get any error through Dokan.

if not self.fs.exists(path):
mode = "w+b"
else:
mode = "r+b"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mode always contains +, meaning it would open file for write. This doesn't work well with read-only file systems 😕

return STATUS_ACCESS_DENIED

retcode = STATUS_SUCCESS
if info.contents.IsDirectory:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While toying with Dokan, I had to change it like this:

if self.fs.isdir(path) or info.contents.IsDirectory:
    info.contents.IsDirectory = True

Otherwise Windows would mistake directories to files and vice versa:

>>> os.path.isfile(r'P:\\')
True
>>> os.path.isdir(r'P:\\')
False

if eno == errno.EACCES:
return ERROR_ACCESS_DENIED
return STATUS_ACCESS_DENIED
return eno

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, it's better to return here some STATUS_* constant to clearly indicate error. Dokan/Windows tend to ignore invalid status codes, as far as I understand, and errno values is mostly invalid as status codes.

In my experiments Dokan could irreversibly hang both the FS process and explorer.exe because of such errors, to the point of inability of reboot Windows.

@Liryna
Copy link
Contributor Author

Liryna commented Nov 19, 2016

@lurch @willmcgugan any news on this ?
There seems to be already a couple of users using it. It would be great to have it in the pyfs master 👍

@Rondom
Copy link

Rondom commented Dec 7, 2016

Any news on this PR?

@wgaylord
Copy link

wgaylord commented Dec 7, 2016 via email

@Rondom
Copy link

Rondom commented Dec 7, 2016

@willmcgugan So, would you prefer this PR to be submitted to pyfilesystem2?

@jeffswt
Copy link

jeffswt commented Dec 21, 2016

Really looking forward to having this in master!

@wgaylord
Copy link

wgaylord commented Jan 7, 2017

.? Any idea about when this might be merged?

@willmcgugan
Copy link
Member

Thanks for the work all. I don't have access to a Windows machine, but if you are all happy with this (@lurch?) I'll go ahead and merge.

@Liryna
Copy link
Contributor Author

Liryna commented Jan 20, 2017

Thanks @willmcgugan I think this can be merged. It has been tested by a couple of users already. @lurch does not look to have the time to test it.

Like I said before, the current implementation of Dokan in the master is extremely old and can no longer be used.
Here I am proposing a working wrapper of the new Dokan version 👍

@jeffswt
Copy link

jeffswt commented Jan 20, 2017

@willmcgugan Tested and secure.

I was wondering if we should drop support for Python 2

@wgaylord
Copy link

I can still confirm that it is working on python3 (3.5.2 (out dated abit..)) and python2.(2.7.13) (Both of which i have installed.)

@willmcgugan
Copy link
Member

Ok, if everyone is confident. Thanks for your work!

@willmcgugan willmcgugan merged commit 44573f7 into PyFilesystem:master Jan 22, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants