Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
436 views
in Technique[技术] by (71.8m points)

winapi - Using a struct as a function argument with the python ctypes module

So I'm attempting to port a python C-extension I wrote for handling junctions/symbolic links/etc on Windows to pure Python using the ctypes module. Unfortunately, since my previous usage of ctypes is pretty limited, I think I may be making a mistake somewhere that is causing my code to function incorrectly. Here's what I have thus far:

from os import path
from ctypes import *
from ctypes.wintypes import *

# Python implementation of:
#
# typedef struct {
#       DWORD   ReparseTag;
#       DWORD   ReparseDataLength;
#       WORD    Reserved;
#       WORD    ReparseTargetLength;
#       WORD    ReparseTargetMaximumLength;
#       WORD    Reserved1;
#       WCHAR   ReparseTarget[1];
# } REPARSE_MOUNTPOINT_DATA_BUFFER, *PREPARSE_MOUNTPOINT_DATA_BUFFER;

class ReparsePoint(Structure):
    _fields_ = [
        ("ReparseTag", DWORD),
        ("ReparseDataLength", DWORD),
        ("Reserved", WORD),

        ("ReparseTargetLength", WORD),
        ("ReparseTargetMaximumLength", WORD),
        ("Reserved1", WORD),
        ("ReparseTarget", c_wchar_p),
    ]

GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000

FILE_SHARE_DELETE = 0x00000004
FILE_SHARE_READ = 0x00000001
FILE_SHARE_WRITE = 0x00000002
FILE_SHARE_READ_WRITE = (FILE_SHARE_READ | FILE_SHARE_WRITE)

OPEN_EXISTING = 3

IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
REPARSE_MOUNTPOINT_HEADER_SIZE = 8

FSCTL_SET_REPARSE_POINT = 589988
FILE_FLAG_OPEN_REPARSE_POINT = 2097152
FILE_FLAG_BACKUP_SEMANTICS = 33554432
FILE_FLAG_REPARSE_BACKUP = 35651584 # FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS

INVALID_HANDLE_VALUE = -1
LPOVERLAPPED = c_void_p
LPSECURITY_ATTRIBUTES = c_void_p

NULL = 0
FALSE = BOOL(0)
TRUE = BOOL(1)

def CreateFile(filename, access, sharemode, creation, flags):
    return HANDLE(windll.kernel32.CreateFileW(
        LPWSTR(filename),
        DWORD(access),
        DWORD(sharemode),
        LPSECURITY_ATTRIBUTES(NULL),
        DWORD(creation),
        DWORD(flags),
        HANDLE(NULL)
    ))

def CreateDirectory(fpath):
    return windll.kernel32.CreateDirectoryW(LPWSTR(fpath), LPSECURITY_ATTRIBUTES(NULL)) != FALSE

def RemoveDirectory(fpath):
    return windll.kernel32.RemoveDirectoryW(LPWSTR(fpath)) != FALSE

def translate_path(fpath):
    fpath = path.abspath(fpath)
    if fpath[len(fpath)-1] == '\' and fpath[len(fpath)-2] == ':':
        fpath = fpath[:len(fpath)-1]
    return '\??\%s' % fpath

def junction(source, link_name):
    """ Create a junction at link_name pointing to source directory. """
    if not path.isdir(source):
        raise Exception('Junction source does not exist or is not a directory.')

    link_name = path.abspath(link_name)
    if path.exists(link_name):
        raise Exception('Filepath for new junction already exists.')

    if not CreateDirectory(link_name):
        raise Exception('Failed to create new directory for target junction.')

    source = translate_path(source)
    hFile = CreateFile(link_name, GENERIC_WRITE, 0, OPEN_EXISTING, FILE_FLAG_REPARSE_BACKUP)
    if hFile == HANDLE(INVALID_HANDLE_VALUE):
        raise Exception('Failed to open directory for junction creation.')

    datalen = len(source) * sizeof(c_wchar)
    reparseInfo = ReparsePoint(
        IO_REPARSE_TAG_MOUNT_POINT,
        datalen + 12,
        0,
        datalen,
        datalen + sizeof(c_wchar),
        0,
        source
    )
    pReparseInfo = pointer(reparseInfo)

    print reparseInfo.ReparseTarget
    returnedLength = DWORD(0)
    result = BOOL(
        windll.kernel32.DeviceIoControl(
            hFile,
            DWORD(FSCTL_SET_REPARSE_POINT),
            pReparseInfo,
            DWORD(reparseInfo.ReparseDataLength + REPARSE_MOUNTPOINT_HEADER_SIZE),
            LPVOID(NULL),
            DWORD(0),
            byref(returnedLength),
            LPOVERLAPPED(NULL)
        )
    ) == TRUE

    #if not result:
        #RemoveDirectory(link_name)

    windll.kernel32.CloseHandle(hFile)
    return result

print junction('G:\cpp.workspace\tools', 'test')
"""
Just putting this here for the moment so I know how to call the function.   

BOOL WINAPI DeviceIoControl(
  __in         HANDLE hDevice,
  __in         DWORD dwIoControlCode,
  __in_opt     LPVOID lpInBuffer,
  __in         DWORD nInBufferSize,
  __out_opt    LPVOID lpOutBuffer,
  __in         DWORD nOutBufferSize,
  __out_opt    LPDWORD lpBytesReturned,
  __inout_opt  LPOVERLAPPED lpOverlapped
);
"""

Currently when run, the code creates a folder for the target junction. (In this case, test) It even seems to be applying the reparse tag to signal to the system that the folder is a junction. However, instead of pointing to "G:cpp.workspaceools" (or rather, to ??G:cpp.workspaceools), the junction appears to be pointing to: ?獬

Now, obviously this is an issue with reparseInfo.ReparseTarget, but I've been unable to figure out what I did wrong.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This is an array of WCHAR, not a pointer:

#       WCHAR   ReparseTarget[1];

The [1] is misleading, the API expects a null-terminated string at the end of the struct.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...