[KERNEL32]
authorPierre Schweitzer <pierre@reactos.org>
Wed, 22 Apr 2015 21:20:35 +0000 (21:20 +0000)
committerPierre Schweitzer <pierre@reactos.org>
Wed, 22 Apr 2015 21:20:35 +0000 (21:20 +0000)
- Halfplement BasepGetComputerNameFromNtPath() which allows querying the computer name given a handle & an NT path
- Implement BasepNotifyTrackingService() which issues a FileTrackingInformation on file move

svn path=/trunk/; revision=67355

reactos/dll/win32/kernel32/client/file/move.c

index 5118a70..0f476ed 100644 (file)
@@ -178,16 +178,312 @@ BasepMoveFileDelayed(IN PUNICODE_STRING ExistingPath,
 
 
 /*
 
 
 /*
- * @unimplemented
+ * @implemented
+ */
+DWORD
+WINAPI
+BasepGetComputerNameFromNtPath(IN PUNICODE_STRING NewPath,
+                               IN HANDLE NewHandle,
+                               OUT PWSTR ComputerName,
+                               IN OUT PULONG ComputerNameLength)
+{
+    BOOL Query = FALSE;
+    WCHAR Letter;
+    PWSTR AbsolutePath, EndOfName;
+    USHORT AbsolutePathLength, NameLength;
+    WCHAR TargetDevice[0x105];
+    WCHAR DeviceName[] = {'A', ':', '\0'}; /* Init to something, will be set later */
+    UNICODE_STRING UncString = RTL_CONSTANT_STRING(L"\\??\\UNC\\");
+    UNICODE_STRING GlobalString = RTL_CONSTANT_STRING(L"\\??\\");
+
+    DPRINT("BasepGetComputerNameFromNtPath(%wZ, %p, %p, %lu)\n", NewPath, NewHandle, ComputerName, ComputerNameLength);
+
+    /* If it's an UNC path */
+    if (RtlPrefixUnicodeString(&UncString, NewPath, TRUE))
+    {
+        /* Check for broken caller */
+        if (NewPath->Length <= UncString.Length)
+        {
+            return ERROR_BAD_PATHNAME;
+        }
+
+        /* Skip UNC prefix */
+        AbsolutePath = &NewPath->Buffer[UncString.Length / sizeof(WCHAR)];
+        AbsolutePathLength = NewPath->Length - UncString.Length;
+
+        /* And query DFS */
+        Query = TRUE;
+    }
+    /* Otherwise, we have to be in global (NT path!), with drive letter */
+    else if (RtlPrefixUnicodeString(&GlobalString, NewPath, TRUE) && NewPath->Buffer[5] == ':')
+    {
+        /* Path is like that: \??\C:\Complete Path\To File.ext */
+        /* Get the letter and upcase it if required */
+        Letter = NewPath->Buffer[4];
+        if (Letter >= 'a' && Letter <= 'z')
+        {
+            Letter -= ('a' - 'A');
+        }
+        DeviceName[0] = Letter;
+
+        /* Query the associated DOS device */
+        if (!QueryDosDeviceW(DeviceName, TargetDevice, ARRAYSIZE(TargetDevice)))
+        {
+            return GetLastError();
+        }
+
+        /* If that's a network share */
+        if (TargetDevice == wcsstr(TargetDevice, L"\\Device\\LanmanRedirector\\;"))
+        {
+            /* Path is like that: \Device\LanmanRedirector\;C:0000000000000000\Complete Path\To File.ext */
+            /* Check we have the correct drive letter */
+            if (TargetDevice[26] == DeviceName[0] &&
+                TargetDevice[27] == ':')
+            {
+                /* Check for the path begin, computer name is before */
+                PWSTR Path = wcschr(&TargetDevice[28], '\\');
+                if (Path == NULL)
+                {
+                    return ERROR_BAD_PATHNAME;
+                }
+
+                AbsolutePath = Path + 1;
+                AbsolutePathLength = sizeof(WCHAR) * (ARRAYSIZE(TargetDevice) - (AbsolutePath - TargetDevice));
+            }
+            else
+            {
+                return ERROR_BAD_PATHNAME;
+            }
+        }
+        /* If it's a local device */
+        else if (TargetDevice == wcsstr(TargetDevice, L"\\Device\\Harddisk")
+                 || TargetDevice == wcsstr(TargetDevice, L"\\Device\\CdRom")
+                 || TargetDevice == wcsstr(TargetDevice, L"\\Device\\Floppy"))
+        {
+            /* Just query the computer name */
+            if (!GetComputerNameW(ComputerName, ComputerNameLength))
+            {
+                return GetLastError();
+            }
+
+            return ERROR_SUCCESS;
+        }
+        /* If it's a DFS share */
+        else if (TargetDevice == wcsstr(TargetDevice, L"\\Device\\WinDfs\\"))
+        {
+            /* Obviously, query DFS */
+            Query = TRUE;
+        }
+        else
+        {
+            return ERROR_BAD_PATHNAME;
+        }
+    }
+    else
+    {
+        return ERROR_BAD_PATHNAME;
+    }
+
+    /* Query DFS, currently not implemented - shouldn't be missing in ReactOS yet ;-) */
+    if (Query)
+    {
+        UNIMPLEMENTED_DBGBREAK("Querying DFS not implemented!\n");
+        AbsolutePath = NULL;
+        AbsolutePathLength = 0;
+    }
+
+    /* Now, properly extract the computer name from the full path */
+    EndOfName = AbsolutePath;
+    if (AbsolutePathLength)
+    {
+        for (NameLength = 0; NameLength < AbsolutePathLength; NameLength += sizeof(WCHAR))
+        {
+            /* Look for the next \, it will be the end of computer name */
+            if (EndOfName[0] == '\\')
+            {
+                break;
+            }
+            /* Computer name cannot contain ., if we get to that point, something went wrong... */
+            else if (EndOfName[0] == '.')
+            {
+                return ERROR_BAD_PATHNAME;
+            }
+
+            ++EndOfName;
+        }
+    }
+
+    NameLength = EndOfName - AbsolutePath;
+    /* Check we didn't overflow and that our computer name isn't ill-formed */
+    if (NameLength >= AbsolutePathLength || NameLength >= MAX_COMPUTERNAME_LENGTH * sizeof(WCHAR))
+    {
+        return ERROR_BAD_PATHNAME;
+    }
+
+    /* Check we can fit */
+    if (NameLength + sizeof(UNICODE_NULL) > *ComputerNameLength * sizeof(WCHAR))
+    {
+        return ERROR_BUFFER_OVERFLOW;
+    }
+
+    /* Write, zero and done! */
+    RtlCopyMemory(ComputerName, AbsolutePath, NameLength);
+    *ComputerNameLength = NameLength / sizeof(WCHAR);
+    ComputerName[NameLength / sizeof(WCHAR)] = UNICODE_NULL;
+
+    return ERROR_SUCCESS;
+}
+
+
+/*
+ * @implemented
  */
 NTSTATUS
 WINAPI
  */
 NTSTATUS
 WINAPI
-BasepNotifyTrackingService(IN PHANDLE ExistingHandle,
+BasepNotifyTrackingService(IN OUT PHANDLE ExistingHandle,
                            IN POBJECT_ATTRIBUTES ObjectAttributes,
                            IN HANDLE NewHandle,
                            IN PUNICODE_STRING NewPath)
 {
                            IN POBJECT_ATTRIBUTES ObjectAttributes,
                            IN HANDLE NewHandle,
                            IN PUNICODE_STRING NewPath)
 {
-    return STATUS_NOT_IMPLEMENTED;
+    NTSTATUS Status;
+    ULONG ComputerNameLength, FileAttributes;
+    WCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
+    OEM_STRING ComputerNameStringA;
+    CHAR ComputerNameStringBuffer[0x105];
+    UNICODE_STRING ComputerNameStringW;
+    IO_STATUS_BLOCK IoStatusBlock;
+    FILE_BASIC_INFORMATION FileBasicInfo;
+    HANDLE hFullWrite;
+    struct
+    {
+        FILE_TRACKING_INFORMATION;
+        CHAR Buffer[(MAX_COMPUTERNAME_LENGTH + 1) * sizeof(WCHAR)];
+    } FileTrackingInfo;
+
+    DPRINT("BasepNotifyTrackingService(%p, %p, %p, %wZ)\n", *ExistingHandle, ObjectAttributes, NewHandle, NewPath);
+
+    Status = STATUS_SUCCESS;
+    ComputerNameLength = ARRAYSIZE(ComputerName);
+
+    /* Attempt to get computer name of target handle */
+    if (BasepGetComputerNameFromNtPath(NewPath, NewHandle, ComputerName, &ComputerNameLength))
+    {
+        /* If we failed to get it, we will just notify with the handle */
+        FileTrackingInfo.ObjectInformationLength = 0;
+    }
+    else
+    {
+        /* Convert the retrieved computer name to ANSI and attach it to the notification */
+        ComputerNameStringA.Length = 0;
+        ComputerNameStringA.MaximumLength = ARRAYSIZE(ComputerNameStringBuffer);
+        ComputerNameStringA.Buffer = ComputerNameStringBuffer;
+
+        RtlInitUnicodeString(&ComputerNameStringW, ComputerName);
+        Status = RtlUnicodeStringToOemString(&ComputerNameStringA, &ComputerNameStringW, 0);
+        if (!NT_SUCCESS(Status))
+        {
+            return Status;
+        }
+
+        RtlCopyMemory(FileTrackingInfo.ObjectInformation, ComputerNameStringA.Buffer, ComputerNameStringA.Length);
+        FileTrackingInfo.ObjectInformation[ComputerNameStringA.Length] = 0;
+        FileTrackingInfo.ObjectInformationLength = ComputerNameStringA.Length + 1;
+    }
+
+    /* Attach the handle we moved */
+    FileTrackingInfo.DestinationFile = NewHandle;
+
+    /* Final, notify */
+    Status = NtSetInformationFile(*ExistingHandle,
+                                  &IoStatusBlock,
+                                  &FileTrackingInfo,
+                                  sizeof(FileTrackingInfo),
+                                  FileTrackingInformation);
+    if (Status != STATUS_ACCESS_DENIED)
+    {
+        return Status;
+    }
+
+    /* If we get here, we got access denied error, this comes from a
+     * read-only flag. So, close the file, in order to reopen it with enough
+     * rights to remove said flag and reattempt notification
+     */
+    CloseHandle(*ExistingHandle);
+
+    /* Reopen it, to be able to change the destination file attributes */
+    Status = NtOpenFile(ExistingHandle,
+                        SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
+                        ObjectAttributes,
+                        &IoStatusBlock,
+                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                        FILE_SYNCHRONOUS_IO_NONALERT);
+    if (!NT_SUCCESS(Status))
+    {
+        *ExistingHandle = INVALID_HANDLE_VALUE;
+        return Status;
+    }
+
+    /* Get the file attributes */
+    Status = NtQueryInformationFile(*ExistingHandle,
+                                    &IoStatusBlock,
+                                    &FileBasicInfo,
+                                    sizeof(FileBasicInfo),
+                                    FileBasicInformation);
+    if (!NT_SUCCESS(Status))
+    {
+        return Status;
+    }
+
+    /* Get rid of the read only flag */
+    FileAttributes = FileBasicInfo.FileAttributes & ~FILE_ATTRIBUTE_READONLY;
+    RtlZeroMemory(&FileBasicInfo, sizeof(FileBasicInfo));
+    FileBasicInfo.FileAttributes = FileAttributes;
+
+    /* Attempt... */
+    Status = NtSetInformationFile(*ExistingHandle,
+                                  &IoStatusBlock,
+                                  &FileBasicInfo,
+                                  sizeof(FileBasicInfo),
+                                  FileBasicInformation);
+    if (!NT_SUCCESS(Status))
+    {
+        return Status;
+    }
+
+    /* Now, reopen with maximum accesses to notify */
+    Status = NtOpenFile(&hFullWrite,
+                        GENERIC_WRITE | SYNCHRONIZE,
+                        ObjectAttributes,
+                        &IoStatusBlock,
+                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                        FILE_SYNCHRONOUS_IO_NONALERT);
+    if (NT_SUCCESS(Status))
+    {
+        NtClose(*ExistingHandle);
+        *ExistingHandle = hFullWrite;
+
+        /* Full success, notify! */
+        Status = NtSetInformationFile(*ExistingHandle,
+                                      &IoStatusBlock,
+                                      &FileTrackingInfo,
+                                      sizeof(FileTrackingInfo),
+                                      FileTrackingInformation);
+    }
+
+    /* If opening with full access failed or if notify failed, restore read-only */
+    if (!NT_SUCCESS(Status))
+    {
+        FileBasicInfo.FileAttributes |= FILE_ATTRIBUTE_READONLY;
+
+        Status = NtSetInformationFile(*ExistingHandle,
+                                      &IoStatusBlock,
+                                      &FileBasicInfo,
+                                      sizeof(FileBasicInfo),
+                                      FileBasicInformation);
+    }
+
+    /* We're done */
+    return Status;
 }
 
 
 }