无法获取 OneDrive 文件夹的重分析点信息

Can't Get Reparse Point Information for the OneDrive Folder

我正在使用下面的代码在我的应用程序中检索重分析点信息。这对符号链接和联结非常有用,但对于 OneDrive 文件夹及其所有子项,'Not a reparse point' 会失败。

   using (SafeFileHandle srcHandle = NativeMethods.CreateFile(@"C:\Users\UserName\OneDrive",
                                                              0,
                                                              System.IO.FileShare.Read,
                                                              IntPtr.Zero,
                                                              System.IO.FileMode.Open,
                                                              NativeMethods.FileFlags.BackupSemantics | NativeMethods.FileFlags.OpenReparsePoint,
                                                              IntPtr.Zero))
    {
         if (!srcHandle.IsInvalid)
         {
              NativeMethods.REPARSE_DATA_BUFFER rdb = new NativeMethods.REPARSE_DATA_BUFFER();
              IntPtr pMem = Marshal.AllocHGlobal(Marshal.SizeOf(rdb) + sizeof(uint) + sizeof(ushort) + sizeof(ushort) + 0xFFFF);

              var outBufferSize = Marshal.SizeOf(typeof(NativeMethods.REPARSE_DATA_BUFFER));
              var outBuffer = Marshal.AllocHGlobal(outBufferSize);

              // Determine if it's a symbolic link or a junction point
              try
              {
                   int bytesRet = 0;
                   if (NativeMethods.DeviceIoControl(srcHandle, NativeMethods.FSCTL_GET_REPARSE_POINT, IntPtr.Zero, 0, outBuffer, outBufferSize, ref bytesRet, IntPtr.Zero) != 0)
                   {
                        rdb = (NativeMethods.REPARSE_DATA_BUFFER)Marshal.PtrToStructure(pMem, rdb.GetType());
                        ...
                   }
                   else     // Fails with ERROR_NOT_A_REPARSE_POINT** (0x1126) on OneDrive folder and all it's child items
                   {
                        log.LogError("FSCTL_GET_REPARSE_POINT error=" + Marshal.GetHRForLastWin32Error());
                   }
              }
              catch (Exception e1)
              {
                   log.LogError("FSCTL_GET_REPARSE_POINT exception error=" + e1.Message + " -> GetLastWin32Error=" + Marshal.GetLastWin32Error().ToString());
              }
              finally
              {
                   Marshal.FreeHGlobal(pMem);
              }
         }
    }

原生声明:

    [DllImport("kernel32.dll", EntryPoint = "CreateFile", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern SafeFileHandle CreateFile(string fileName, FileAccessAPI desiredAccess, FileShare shareMode, IntPtr secAttrib, FileMode createDisp, FileFlags flags, IntPtr template);

    public const int FSCTL_GET_REPARSE_POINT = 0x000900A8;

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern unsafe int DeviceIoControl(SafeFileHandle hFile,
                                                    int control,
                                                    IntPtr inbuffer,
                                                    int bufferSize,
                                                    IntPtr outBuffer,
                                                    int outBufferSize,
                                                    ref int bytesRet,
                                                    IntPtr overlapped);

    public const uint RP_SYMBOLICLINK   = 0xA000000C;
    public const uint RP_JUNCTION       = 0xA0000003;
    public const uint RP_REPARSETAG_WCI = 0x80000018;
    public const uint RP_REPARSETAG_APP = 0x8000001b;
    public const uint RP_CLOUD          = 0x9000001A;
    public const uint RP_CLOUD_1        = 0x9000101A;
    ...

    [StructLayout(LayoutKind.Sequential)]
     public struct REPARSE_DATA_BUFFER
     {
          public uint   ReparseTag;
          public ushort ReparseDataLength;
          public ushort Reserved;
          public ushort SubstituteNameOffset;
          public ushort SubstituteNameLength;
          public ushort PrintNameOffset;
          public ushort PrintNameLength;
          [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0xFFF0)]
          public byte[] PathBuffer;
     }

     [Flags()]
     public enum FileFlags : uint
     {
          ...
          OpenReparsePoint    = 0x00200000,
          BackupSemantics     = 0x02000000,
     }

以下命令可以成功检索 OneDrive 文件夹的重分析点信息。

fsutil reparsepoint 查询 C:\Users\UserName\OneDrive

如果能确定如何让这段代码发挥作用,那将非常有用。非常令人沮丧的是,已确认具有重新分析点的文件夹会收到一条错误消息,表明它们没有。

我也在 C++ 中尝试过,但得到了同样的错误。

使用一些相关的 API 对其进行了测试。如有问题,欢迎指出。

使用GetFileAttributes得到的文件属性:

FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_READONLY

但没有属性:FILE_ATTRIBUTE_REPARSE_POINT。而OneDrive中的普通文件只有属性:

FILE_ATTRIBUTE_ARCHIVE 

因此,OneDrive 文件夹及其所有子项没有重新分析点 属性。

这里是测试用例:

#include <windows.h>
#include <iostream>
typedef struct _REPARSE_DATA_BUFFER {
    ULONG  ReparseTag;
    USHORT ReparseDataLength;
    USHORT Reserved;
    union {
        struct {
            USHORT SubstituteNameOffset;
            USHORT SubstituteNameLength;
            USHORT PrintNameOffset;
            USHORT PrintNameLength;
            ULONG  Flags;
            WCHAR  PathBuffer[1];
        } SymbolicLinkReparseBuffer;
        struct {
            USHORT SubstituteNameOffset;
            USHORT SubstituteNameLength;
            USHORT PrintNameOffset;
            USHORT PrintNameLength;
            WCHAR  PathBuffer[1];
        } MountPointReparseBuffer;
        struct {
            UCHAR DataBuffer[1];
        } GenericReparseBuffer;
    } DUMMYUNIONNAME;
} REPARSE_DATA_BUFFER, * PREPARSE_DATA_BUFFER;

int main()
{
    DWORD attr = GetFileAttributes(TEXT("C:\Users\UserName\OneDrive"));
    if (attr & FILE_ATTRIBUTE_REPARSE_POINT)
        printf("with Attributes: FILE_ATTRIBUTE_REPARSE_POINT\n");
    else
        printf("without FILE_ATTRIBUTE_REPARSE_POINT, Attributes = %x\n",attr);
    HANDLE hFile = CreateFile(
        TEXT("C:\Users\UserName\OneDrive"),
        0,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_BACKUP_SEMANTICS| FILE_FLAG_OPEN_REPARSE_POINT,
        NULL
        );
    if (hFile != INVALID_HANDLE_VALUE)
    {
        REPARSE_DATA_BUFFER *rdb = (REPARSE_DATA_BUFFER*)malloc(sizeof(REPARSE_DATA_BUFFER)+ sizeof(ULONG)+sizeof(USHORT)+0xffff);
        if (rdb)
        {
            DWORD outBufferSize = sizeof(REPARSE_DATA_BUFFER) + sizeof(ULONG) + sizeof(USHORT) + 0xffff;
            DWORD bytesRet = 0;
            if (DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, NULL, 0, rdb, outBufferSize, &bytesRet, NULL))
                wprintf(L"DeviceIoControl succeed! printfname = %s\n", rdb->MountPointReparseBuffer.PathBuffer[rdb->MountPointReparseBuffer.PrintNameLength / sizeof(WCHAR)]);
            else
                wprintf(L"error code = %d\n", GetLastError());
            free(rdb);
            rdb = NULL;
        }
        else
            printf("malloc failed\n");
    }
}

并且使用fsutil reparsepoint query "C:\Users\UserName\OneDrive",输出总是如下:

基本没有可用的信息。此外,关闭 "Files On Demand" 将删除重新分析点。

编辑:

不同于GetFileAttributesFindFirstFile can get this FILE_ATTRIBUTE_REPARSE_POINT attribute. According to the document Reparse Point Tags

To retrieve the reparse point tag, use the FindFirstFile function. If the dwFileAttributes member includes the FILE_ATTRIBUTE_REPARSE_POINT attribute, then the dwReserved0 member specifies the reparse point.

更新:

经与相关工程师确认后,此问题由于云文件会隐藏重解析信息,下面是相同的文档 为了克服这个问题,他们有两种方法。

  1. 将执行文件放在%systemroot%下
  2. 在调用reparse相关之前先调用下面几行代码activity

https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-rtlsetprocessplaceholdercompatibilitymode

typedef NTSYSAPI CHAR(*PGNSI)(CHAR Mode);
#define PHCM_EXPOSE_PLACEHOLDERS    ((CHAR)2)
HMODULE hmod = LoadLibrary(L"ntdll.dll");
if (hmod == NULL)
{
    wprintf(L"LoadLibrary failed with %u\n", GetLastError());
    return 0;
}

PGNSI pGNSI;
pGNSI = (PGNSI)GetProcAddress(hmod,"RtlSetProcessPlaceholderCompatibilityMode");
if (pGNSI == NULL)
{
    wprintf(L"GetProcAddress failed with %u\n", GetLastError());
    return 0;
}
CHAR c = pGNSI(PHCM_EXPOSE_PLACEHOLDERS);

文档:

https://docs.microsoft.com/en-us/windows/win32/cfapi/build-a-cloud-file-sync-engine#compatibility-with-applications-that-use-reparse-points

Compatibility with applications that use reparse points

The cloud files API implements the placeholder system using reparse points. A common misconception about reparse points is that they are the same as symbolic links. This misconception is occasionally reflected in application implementations, and as a result, many existing applications hit errors when encountering any reparse point.

To mitigate this compatibility issue, the cloud files API always hides its reparse points from all applications except for sync engines and processes whose main image resides under %systemroot%. Applications that understand reparse points correctly can force the platform to expose cloud files API reparse points using RtlSetProcessPlaceholderCompatibilityMode or RtlSetThreadProcessPlaceholderCompatibilityMode.