* Sync up to trunk head (r60691).
[reactos.git] / ntoskrnl / ob / oblink.c
index 5b14747..ce4c8c6 100644 (file)
@@ -23,8 +23,160 @@ VOID
 NTAPI
 ObpDeleteSymbolicLinkName(IN POBJECT_SYMBOLIC_LINK SymbolicLink)
 {
-    /* FIXME: Device maps not supported yet */
+    POBJECT_HEADER ObjectHeader;
+    POBJECT_HEADER_NAME_INFO ObjectNameInfo;
+
+    /* FIXME: Need to support Device maps */
+
+    /* Get header data */
+    ObjectHeader = OBJECT_TO_OBJECT_HEADER(SymbolicLink);
+    ObjectNameInfo = ObpReferenceNameInfo(ObjectHeader);
+
+    /* Check if we are not actually in a directory with a device map */
+    if (!(ObjectNameInfo) ||
+        !(ObjectNameInfo->Directory) /*||
+        !(ObjectNameInfo->Directory->DeviceMap)*/)
+    {
+        ObpDereferenceNameInfo(ObjectNameInfo);
+        return;
+    }
 
+    /* Check if it's a DOS drive letter, and remove the entry from drive map if needed */
+    if (SymbolicLink->DosDeviceDriveIndex != 0 &&
+        ObjectNameInfo->Name.Length == 2 * sizeof(WCHAR) &&
+        ObjectNameInfo->Name.Buffer[1] == L':' &&
+        ( (ObjectNameInfo->Name.Buffer[0] >= L'A' &&
+            ObjectNameInfo->Name.Buffer[0] <= L'Z') ||
+          (ObjectNameInfo->Name.Buffer[0] >= L'a' &&
+            ObjectNameInfo->Name.Buffer[0] <= L'z') ))
+    {
+        /* Remove the drive entry */
+        KeAcquireGuardedMutex(&ObpDeviceMapLock);
+        ObSystemDeviceMap->DriveType[SymbolicLink->DosDeviceDriveIndex-1] =
+            DOSDEVICE_DRIVE_UNKNOWN;
+        ObSystemDeviceMap->DriveMap &=
+            ~(1 << (SymbolicLink->DosDeviceDriveIndex-1));
+        KeReleaseGuardedMutex(&ObpDeviceMapLock);
+
+        /* Reset the drive index, valid drive index starts from 1 */
+        SymbolicLink->DosDeviceDriveIndex = 0;
+    }
+
+    ObpDereferenceNameInfo(ObjectNameInfo);
+}
+
+NTSTATUS
+NTAPI
+ObpParseSymbolicLinkToIoDeviceObject(IN POBJECT_DIRECTORY SymbolicLinkDirectory,
+                                     IN OUT POBJECT_DIRECTORY *Directory,
+                                     IN OUT PUNICODE_STRING TargetPath,
+                                     IN OUT POBP_LOOKUP_CONTEXT Context,
+                                     OUT PVOID *Object)
+{
+    UNICODE_STRING Name;
+    BOOLEAN ManualUnlock;
+
+    if (! TargetPath || ! Object || ! Context || ! Directory ||
+        ! SymbolicLinkDirectory)
+    {
+        return STATUS_INVALID_PARAMETER;
+    }
+
+    /* FIXME: Need to support Device maps */
+
+    /* Try walking the target path and open each part of the path */
+    while (TargetPath->Length)
+    {
+        /* Strip '\' if present at the beginning of the target path */
+        if (TargetPath->Length >= sizeof(OBJ_NAME_PATH_SEPARATOR)&&
+            TargetPath->Buffer[0] == OBJ_NAME_PATH_SEPARATOR)
+        {
+            TargetPath->Buffer++;
+            TargetPath->Length -= sizeof(OBJ_NAME_PATH_SEPARATOR);
+        }
+
+        /* Remember the current component of the target path */
+        Name = *TargetPath;
+
+        /* Move forward to the next component of the target path */
+        while (TargetPath->Length >= sizeof(OBJ_NAME_PATH_SEPARATOR))
+        {
+            if (TargetPath->Buffer[0] != OBJ_NAME_PATH_SEPARATOR)
+            {
+                TargetPath->Buffer++;
+                TargetPath->Length -= sizeof(OBJ_NAME_PATH_SEPARATOR);
+            }
+            else
+                break;
+        }
+
+        Name.Length -= TargetPath->Length;
+
+        /* Finished processing the entire path, stop */
+        if (! Name.Length)
+            break;
+
+        /*
+         * Make sure a deadlock does not occur as an exclusive lock on a pushlock
+         * would have already taken one in ObpLookupObjectName() on the parent
+         * directory where the symlink is being created [ObInsertObject()].
+         * Prevent recursive locking by faking lock state in the lookup context
+         * when the current directory is same as the parent directory where
+         * the symlink is being created. If the lock state is not faked,
+         * ObpLookupEntryDirectory() will try to get a recursive lock on the
+         * pushlock and hang. For e.g. this happens when a substed drive is pointed to
+         * another substed drive.
+         */
+        if (*Directory == SymbolicLinkDirectory && ! Context->DirectoryLocked)
+        {
+            /* Fake lock state so that ObpLookupEntryDirectory() doesn't attempt a lock */
+            ManualUnlock = TRUE;
+            Context->DirectoryLocked = TRUE;
+        }
+        else
+            ManualUnlock = FALSE;
+
+        *Object = ObpLookupEntryDirectory(*Directory,
+                                          &Name,
+                                          0,
+                                          FALSE,
+                                          Context);
+
+        /* Locking was faked, undo it now */
+        if (*Directory == SymbolicLinkDirectory && ManualUnlock)
+            Context->DirectoryLocked = FALSE;
+
+        /* Lookup failed, stop */
+        if (! *Object)
+            break;
+
+        if (OBJECT_TO_OBJECT_HEADER(*Object)->Type == ObDirectoryType)
+        {
+            /* Make this current directory, and continue search */
+            *Directory = (POBJECT_DIRECTORY)*Object;
+        }
+        else if (OBJECT_TO_OBJECT_HEADER(*Object)->Type == ObSymbolicLinkType &&
+            (((POBJECT_SYMBOLIC_LINK)*Object)->DosDeviceDriveIndex == 0))
+        {
+            /* Symlink points to another initialized symlink, ask caller to reparse */
+            *Directory = ObpRootDirectoryObject;
+            TargetPath = &((POBJECT_SYMBOLIC_LINK)*Object)->LinkTarget;
+            return STATUS_REPARSE_OBJECT;
+        }
+        else
+        {
+            /* Neither directory or symlink, stop */
+            break;
+        }
+    }
+
+    /* Return a valid object, only if object type is IoDeviceObject */
+    if (*Object &&
+        OBJECT_TO_OBJECT_HEADER(*Object)->Type != IoDeviceObjectType)
+    {
+        *Object = NULL;
+    }
+    return STATUS_SUCCESS;
 }
 
 VOID
@@ -33,23 +185,124 @@ ObpCreateSymbolicLinkName(IN POBJECT_SYMBOLIC_LINK SymbolicLink)
 {
     POBJECT_HEADER ObjectHeader;
     POBJECT_HEADER_NAME_INFO ObjectNameInfo;
+    PVOID Object = NULL;
+    POBJECT_DIRECTORY Directory;
+    UNICODE_STRING TargetPath;
+    NTSTATUS Status;
+    ULONG DriveType = DOSDEVICE_DRIVE_CALCULATE;
+    ULONG ReparseCnt;
+    const ULONG MaxReparseAttempts = 20;
+    OBP_LOOKUP_CONTEXT Context;
+
+    /* FIXME: Need to support Device maps */
 
     /* Get header data */
     ObjectHeader = OBJECT_TO_OBJECT_HEADER(SymbolicLink);
-    ObjectNameInfo = OBJECT_HEADER_TO_NAME_INFO(ObjectHeader);
+    ObjectNameInfo = ObpReferenceNameInfo(ObjectHeader);
 
     /* Check if we are not actually in a directory with a device map */
     if (!(ObjectNameInfo) ||
-        !(ObjectNameInfo->Directory) ||
-        !(ObjectNameInfo->Directory->DeviceMap))
+        !(ObjectNameInfo->Directory) /*||
+        !(ObjectNameInfo->Directory->DeviceMap)*/)
     {
-        /* There's nothing to do, return */
+        ObpDereferenceNameInfo(ObjectNameInfo);
         return;
     }
 
-    /* FIXME: We don't support device maps yet */
-    DPRINT1("Unhandled path!\n");
-    ASSERT(FALSE);
+    /* Check if it's a DOS drive letter, and set the drive index accordingly */
+    if (ObjectNameInfo->Name.Length == 2 * sizeof(WCHAR) &&
+        ObjectNameInfo->Name.Buffer[1] == L':' &&
+        ( (ObjectNameInfo->Name.Buffer[0] >= L'A' &&
+            ObjectNameInfo->Name.Buffer[0] <= L'Z') ||
+          (ObjectNameInfo->Name.Buffer[0] >= L'a' &&
+            ObjectNameInfo->Name.Buffer[0] <= L'z') ))
+    {
+        SymbolicLink->DosDeviceDriveIndex =
+            RtlUpcaseUnicodeChar(ObjectNameInfo->Name.Buffer[0]) - L'A';
+        /* The Drive index start from 1 */
+        SymbolicLink->DosDeviceDriveIndex++;
+
+        /* Initialize lookup context */
+        ObpInitializeLookupContext(&Context);
+
+        /* Start the search from the root */
+        Directory = ObpRootDirectoryObject;
+        TargetPath = SymbolicLink->LinkTarget;
+
+        /*
+         * Locate the IoDeviceObject if any this symbolic link points to.
+         * To prevent endless reparsing, setting an upper limit on the 
+         * number of reparses.
+         */
+        Status = STATUS_REPARSE_OBJECT;
+        ReparseCnt = 0;
+        while (Status == STATUS_REPARSE_OBJECT &&
+               ReparseCnt < MaxReparseAttempts)
+        {
+            Status =
+                ObpParseSymbolicLinkToIoDeviceObject(ObjectNameInfo->Directory,
+                                                     &Directory,
+                                                     &TargetPath,
+                                                     &Context,
+                                                     &Object);
+            if (Status == STATUS_REPARSE_OBJECT)
+                ReparseCnt++;
+        }
+
+        /* Cleanup lookup context */
+        ObpReleaseLookupContext(&Context);
+
+        /* Error, or max resparse attemtps exceeded */
+        if (! NT_SUCCESS(Status) || ReparseCnt >= MaxReparseAttempts)
+        {
+            /* Cleanup */
+            ObpDereferenceNameInfo(ObjectNameInfo);
+            return;
+        }
+
+        if (Object)
+        {
+            /* Calculate the drive type */
+            switch(((PDEVICE_OBJECT)Object)->DeviceType)
+            {
+            case FILE_DEVICE_VIRTUAL_DISK:
+                DriveType = DOSDEVICE_DRIVE_RAMDISK;
+                break;
+            case FILE_DEVICE_CD_ROM:
+            case FILE_DEVICE_CD_ROM_FILE_SYSTEM:
+                DriveType = DOSDEVICE_DRIVE_CDROM;
+                break;
+            case FILE_DEVICE_DISK:
+            case FILE_DEVICE_DISK_FILE_SYSTEM:
+            case FILE_DEVICE_FILE_SYSTEM:
+                if (((PDEVICE_OBJECT)Object)->Characteristics & FILE_REMOVABLE_MEDIA)
+                    DriveType = DOSDEVICE_DRIVE_REMOVABLE;
+                else
+                    DriveType = DOSDEVICE_DRIVE_FIXED;
+                break;
+            case FILE_DEVICE_NETWORK:
+            case FILE_DEVICE_NETWORK_FILE_SYSTEM:
+                DriveType = DOSDEVICE_DRIVE_REMOTE;
+                break;
+            default:
+                DPRINT1("Device Type %lu for %wZ is not known or unhandled\n",
+                        ((PDEVICE_OBJECT)Object)->DeviceType,
+                        &SymbolicLink->LinkTarget);
+                DriveType = DOSDEVICE_DRIVE_UNKNOWN;
+            }
+        }
+
+        /* Add a new drive entry */
+        KeAcquireGuardedMutex(&ObpDeviceMapLock);
+        ObSystemDeviceMap->DriveType[SymbolicLink->DosDeviceDriveIndex-1] =
+            (UCHAR)DriveType;
+        ObSystemDeviceMap->DriveMap |=
+            1 << (SymbolicLink->DosDeviceDriveIndex-1);
+        KeReleaseGuardedMutex(&ObpDeviceMapLock);
+    }
+
+    /* Cleanup */
+    ObpDereferenceNameInfo(ObjectNameInfo);
 }
 
 /*++
@@ -121,7 +374,7 @@ ObpParseSymbolicLink(IN PVOID ParsedObject,
     POBJECT_SYMBOLIC_LINK SymlinkObject = (POBJECT_SYMBOLIC_LINK)ParsedObject;
     PUNICODE_STRING TargetPath;
     PWSTR NewTargetPath;
-    ULONG LengthUsed, MaximumLength;
+    ULONG LengthUsed, MaximumLength, TempLength;
     NTSTATUS Status;
     PAGED_CODE();
 
@@ -158,9 +411,39 @@ ObpParseSymbolicLink(IN PVOID ParsedObject,
         return STATUS_OBJECT_TYPE_MISMATCH;
     }
 
+    /* Check if this symlink is bound to a specific object */
+    if (SymlinkObject->LinkTargetObject)
+    {
+        UNIMPLEMENTED;
+    }
+
     /* Set the target path and length */
     TargetPath = &SymlinkObject->LinkTarget;
-    LengthUsed = TargetPath->Length + RemainingName->Length;
+    TempLength = TargetPath->Length;
+
+    /*
+     * Strip off the extra trailing '\', if we don't do this we will end up
+     * adding a extra '\' between TargetPath and RemainingName
+     * causing caller's like ObpLookupObjectName() to fail.
+     */
+    if (TempLength && RemainingName->Length)
+    {
+        /* The target and remaining names aren't empty, so check for slashes */
+        if ((TargetPath->Buffer[TempLength / sizeof(WCHAR) - 1] ==
+            OBJ_NAME_PATH_SEPARATOR) &&
+            (RemainingName->Buffer[0] == OBJ_NAME_PATH_SEPARATOR))
+        {
+            /* Reduce the length by one to cut off the extra '\' */
+            TempLength -= sizeof(OBJ_NAME_PATH_SEPARATOR);
+        }
+    }
+
+    /* Calculate the new length */
+    LengthUsed = TempLength + RemainingName->Length;
+
+    /* Check if it's not too much */
+    if (LengthUsed > 0xFFF0)
+        return STATUS_NAME_TOO_LONG;
 
     /* Optimization: check if the new name is shorter */
     if (FullPath->MaximumLength <= LengthUsed)
@@ -183,13 +466,13 @@ ObpParseSymbolicLink(IN PVOID ParsedObject,
     if (RemainingName->Length)
     {
         /* Copy the new path */
-        RtlMoveMemory((PVOID)((ULONG_PTR)NewTargetPath + TargetPath->Length),
+        RtlMoveMemory((PVOID)((ULONG_PTR)NewTargetPath + TempLength),
                       RemainingName->Buffer,
                       RemainingName->Length);
     }
 
     /* Copy the target path and null-terminate it */
-    RtlCopyMemory(NewTargetPath, TargetPath->Buffer, TargetPath->Length);
+    RtlCopyMemory(NewTargetPath, TargetPath->Buffer, TempLength);
     NewTargetPath[LengthUsed / sizeof(WCHAR)] = UNICODE_NULL;
 
     /* If the optimization didn't work, free the old buffer */
@@ -312,7 +595,12 @@ NtCreateSymbolicLinkObject(OUT PHANDLE LinkHandle,
             ExAllocatePoolWithTag(PagedPool,
                                   CapturedLinkTarget.MaximumLength,
                                   TAG_SYMLINK_TARGET);
-        if (!SymbolicLink->LinkTarget.Buffer) return STATUS_NO_MEMORY;
+        if (!SymbolicLink->LinkTarget.Buffer)
+        {
+            /* Dereference the symbolic link object and fail */
+            ObDereferenceObject(SymbolicLink);
+            return STATUS_NO_MEMORY;
+        }
 
         /* Copy it */
         RtlCopyMemory(SymbolicLink->LinkTarget.Buffer,