There is also missing functionality in ReactOS, like missing commit charge accounting. I'm sure you know how this works, and that the implementation of the required functions is not a problem at all, but calling the functions from ALL locations where it needs to be done and not doing it wrong, is the problem. Sadly ARM3 is not really good at factoring code, so that handling stuff like this would be easy. Instead code is duplicated in all sorts of locations, requiring to handle other thing in multiple locations as well, instead of centralizing it. And I think I do know the reason why it is done like that: It's the principle: don't change existing code, rather copy and paste 100 lines into another location, if you need it, so it is at least guaranteed, that existing code won't change behavior. This seems to be the MS way of handling things, to make sure to never break existing applications. But for us there is no reason to do so. Factoring the code in a more optimal way would be beneficial, since it will a) help to reduce the places where we introduce bugs, b) help implementing features like commit charge accounting, that benefit from cenralized code.

There are always good reasons for my changes. Even refactoring the code is a good reason, but that is not the final goal of my changes here. You know that I do not buy the "Windows does it like this" argument. At least not as countering a real, practical argument. So you need to find something better :)

And why do you assume, I would not have even bothered "to think why there's a reason why the code was duplicated"? Of course I have. And I came to the conclusion that there was no good reason for us to do it the same way. Or can you please explain why copying basically the same code into multiple locations is alternativeless, or why I am too stupid to refactor that code to remove the code duplication? If you can't, then I don't see any argument, other than complaining that someone changed your code. I do very well undertand that it's annoying when people change your code for no apparent reason, but that's not the case here. And you should know that.

Regarding "fixing those seems to make more sense than refactoring", I can only say that we all could have flooded you with this argument when you were working on ARM3. You broke all kinds of things and some of them are still broken. And that was not hypothetical things, it was real things! You definately killed the AMD64 port, you killed most paging and introduced kernel crashes on too much memory usage. So unless you now want to go ahead and fix everything you broke, I don't buy your argument "fixing things is better than refactoring". Go ahead and fix things, then others might follow your example. :-P

Timo


Am 11.10.2014 18:41, schrieb Alex Ionescu:
I think it would be really great that instead of combining functions together for "removing code duplication" and not even bothering to think why there's a reason why the code was duplicated, people would work on actual improvements like Jerome or Pierre, instead of re-factoring code that works just to make it 'simpler'. Especially since this introduced a few bugs which had to be later fixed. There's a million bugs in ReactOS, including in the memory manager, so fixing those seems to make more sense than refactoring.

Just my 2 cents.

Best regards,
Alex Ionescu

On Tue, Oct 7, 2014 at 5:31 PM, <tkreuzer@svn.reactos.org> wrote:
Author: tkreuzer
Date: Wed Oct  8 00:31:17 2014
New Revision: 64589

URL: http://svn.reactos.org/svn/reactos?rev=64589&view=rev
Log:
[NTOSKRNL]
Implement MiInsertVadEx, replacing duplicated code from NtAllocateVirtualMemory and MiMapViewOfDataSection

Modified:
    trunk/reactos/ntoskrnl/mm/ARM3/miarm.h
    trunk/reactos/ntoskrnl/mm/ARM3/section.c
    trunk/reactos/ntoskrnl/mm/ARM3/vadnode.c
    trunk/reactos/ntoskrnl/mm/ARM3/virtual.c

Modified: trunk/reactos/ntoskrnl/mm/ARM3/miarm.h
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/ntoskrnl/mm/ARM3/miarm.h?rev=64589&r1=64588&r2=64589&view=diff
==============================================================================
--- trunk/reactos/ntoskrnl/mm/ARM3/miarm.h      [iso-8859-1] (original)
+++ trunk/reactos/ntoskrnl/mm/ARM3/miarm.h      [iso-8859-1] Wed Oct  8 00:31:17 2014
@@ -2144,6 +2144,16 @@
     IN PEPROCESS Process
 );

+NTSTATUS
+NTAPI
+MiInsertVadEx(
+    _Inout_ PMMVAD Vad,
+    _In_ ULONG_PTR *BaseAddress,
+    _In_ SIZE_T ViewSize,
+    _In_ ULONG_PTR HighestAddress,
+    _In_ ULONG_PTR Alignment,
+    _In_ ULONG AllocationType);
+
 VOID
 NTAPI
 MiInsertBasedSection(

Modified: trunk/reactos/ntoskrnl/mm/ARM3/section.c
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/ntoskrnl/mm/ARM3/section.c?rev=64589&r1=64588&r2=64589&view=diff
==============================================================================
--- trunk/reactos/ntoskrnl/mm/ARM3/section.c    [iso-8859-1] (original)
+++ trunk/reactos/ntoskrnl/mm/ARM3/section.c    [iso-8859-1] Wed Oct  8 00:31:17 2014
@@ -1252,9 +1252,7 @@
                        IN ULONG AllocationType)
 {
     PMMVAD_LONG Vad;
-    PETHREAD Thread = PsGetCurrentThread();
-    PMMSUPPORT AddressSpace;
-    ULONG_PTR StartAddress, EndingAddress;
+    ULONG_PTR StartAddress;
     ULONG_PTR ViewSizeInPages;
     PSUBSECTION Subsection;
     PSEGMENT Segment;
@@ -1263,8 +1261,6 @@
     ULONG QuotaCharge = 0, QuotaExcess = 0;
     PMMPTE PointerPte, LastPte;
     MMPTE TempPte;
-    PMMADDRESS_NODE Parent;
-    TABLE_SEARCH_RESULT Result;
     DPRINT("Mapping ARM3 data section\n");

     /* Get the segment for this section */
@@ -1361,6 +1357,7 @@

     /* Write all the data required in the VAD for handling a fault */
     Vad->ControlArea = ControlArea;
+    Vad->u.VadFlags.CommitCharge = 0;
     Vad->u.VadFlags.Protection = ProtectionMask;
     Vad->u2.VadFlags2.FileOffset = (ULONG)(SectionOffset->QuadPart >> 16);
     Vad->u2.VadFlags2.Inherit = (InheritDisposition == ViewShare);
@@ -1437,100 +1434,23 @@
         StartAddress = 0;
     }

-    /* Lock the address space and make sure the process is alive */
-    AddressSpace = MmGetCurrentAddressSpace();
-    MmLockAddressSpace(AddressSpace);
-    if (Process->VmDeleted)
-    {
-        MmUnlockAddressSpace(AddressSpace);
-        ExFreePoolWithTag(Vad, 'ldaV');
-        DPRINT1("The process is dying\n");
-        return STATUS_PROCESS_IS_TERMINATING;
-    }
-
-    /* Did the caller specify an address? */
-    if (StartAddress == 0)
-    {
-        /* ARM3 does not support these flags yet */
-        ASSERT(Process->VmTopDown == 0);
-        ASSERT(ZeroBits == 0);
-
-        /* Which way should we search? */
-        if (AllocationType & MEM_TOP_DOWN)
-        {
-            /* No, find an address top-down */
-            Result = MiFindEmptyAddressRangeDownTree(*ViewSize,
-                                                     (ULONG_PTR)MM_HIGHEST_VAD_ADDRESS,
-                                                     _64K,
-                                                     &Process->VadRoot,
-                                                     &StartAddress,
-                                                     &Parent);
-        }
-        else
-        {
-            /* No, find an address bottom-up */
-            Result = MiFindEmptyAddressRangeInTree(*ViewSize,
-                                                   _64K,
-                                                   &Process->VadRoot,
-                                                   &Parent,
-                                                   &StartAddress);
-        }
-
-        /* Check if we found a suitable location */
-        if (Result == TableFoundNode)
-        {
-            DPRINT1("Not enough free space to insert this section!\n");
-            MmUnlockAddressSpace(AddressSpace);
-            MiDereferenceControlArea(ControlArea);
-            ExFreePoolWithTag(Vad, 'ldaV');
-            return STATUS_CONFLICTING_ADDRESSES;
-        }
-
-        /* Get the ending address, which is the last piece we need for the VAD */
-        EndingAddress = StartAddress + (ViewSizeInPages * PAGE_SIZE) - 1;
-    }
-    else
-    {
-        /* Get the ending address, which is the last piece we need for the VAD */
-        EndingAddress = StartAddress + (ViewSizeInPages * PAGE_SIZE)  - 1;
-
-        /* Make sure it doesn't conflict with an existing allocation */
-        Result = MiCheckForConflictingNode(StartAddress >> PAGE_SHIFT,
-                                           EndingAddress >> PAGE_SHIFT,
-                                           &Process->VadRoot,
-                                           &Parent);
-        if (Result == TableFoundNode)
-        {
-            DPRINT1("Conflict with SEC_BASED or manually based section!\n");
-            MmUnlockAddressSpace(AddressSpace);
-            MiDereferenceControlArea(ControlArea);
-            ExFreePoolWithTag(Vad, 'ldaV');
-            return STATUS_CONFLICTING_ADDRESSES;
-        }
-    }
-
-    /* Now set the VAD address */
-    Vad->StartingVpn = StartAddress >> PAGE_SHIFT;
-    Vad->EndingVpn = EndingAddress >> PAGE_SHIFT;
-
-    /* Pretend as if we own the working set */
-    MiLockProcessWorkingSetUnsafe(Process, Thread);
-
     /* Insert the VAD */
-    Process->VadRoot.NodeHint = Vad;
-    MiInsertNode(&Process->VadRoot, (PVOID)Vad, Parent, Result);
-
-    /* Release the working set */
-    MiUnlockProcessWorkingSetUnsafe(Process, Thread);
-
-    /* Unlock the address space */
-    MmUnlockAddressSpace(AddressSpace);
+    Status = MiInsertVadEx((PMMVAD)Vad,
+                           &StartAddress,
+                           ViewSizeInPages * PAGE_SIZE,
+                           MAXULONG_PTR >> ZeroBits,
+                           MM_VIRTMEM_GRANULARITY,
+                           AllocationType);
+    if (!NT_SUCCESS(Status))
+    {
+        return Status;
+    }

     /* Windows stores this for accounting purposes, do so as well */
     if (!Segment->u2.FirstMappedVa) Segment->u2.FirstMappedVa = (PVOID)StartAddress;

     /* Finally, let the caller know where, and for what size, the view was mapped */
-    *ViewSize = (ULONG_PTR)EndingAddress - (ULONG_PTR)StartAddress + 1;
+    *ViewSize = ViewSizeInPages * PAGE_SIZE;
     *BaseAddress = (PVOID)StartAddress;
     DPRINT("Start and region: 0x%p, 0x%p\n", *BaseAddress, *ViewSize);
     return STATUS_SUCCESS;

Modified: trunk/reactos/ntoskrnl/mm/ARM3/vadnode.c
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/ntoskrnl/mm/ARM3/vadnode.c?rev=64589&r1=64588&r2=64589&view=diff
==============================================================================
--- trunk/reactos/ntoskrnl/mm/ARM3/vadnode.c    [iso-8859-1] (original)
+++ trunk/reactos/ntoskrnl/mm/ARM3/vadnode.c    [iso-8859-1] Wed Oct  8 00:31:17 2014
@@ -193,6 +193,138 @@
     MiInsertNode(&Process->VadRoot, (PVOID)Vad, Parent, Result);
 }

+NTSTATUS
+NTAPI
+MiInsertVadEx(
+    _Inout_ PMMVAD Vad,
+    _In_ ULONG_PTR *BaseAddress,
+    _In_ SIZE_T ViewSize,
+    _In_ ULONG_PTR HighestAddress,
+    _In_ ULONG_PTR Alignment,
+    _In_ ULONG AllocationType)
+{
+    ULONG_PTR StartingAddress, EndingAddress;
+    PEPROCESS CurrentProcess;
+    PETHREAD CurrentThread;
+    TABLE_SEARCH_RESULT Result;
+    PMMADDRESS_NODE Parent;
+
+    /* Align the view size to pages */
+    ViewSize = ALIGN_UP_BY(ViewSize, PAGE_SIZE);
+
+    /* Get the current process */
+    CurrentProcess = PsGetCurrentProcess();
+
+    /* Acquire the address creation lock and make sure the process is alive */
+    KeAcquireGuardedMutex(&CurrentProcess->AddressCreationLock);
+    if (CurrentProcess->VmDeleted)
+    {
+        KeReleaseGuardedMutex(&CurrentProcess->AddressCreationLock);
+        DPRINT1("The process is dying\n");
+        return STATUS_PROCESS_IS_TERMINATING;
+    }
+
+    /* Did the caller specify an address? */
+    if (*BaseAddress == 0)
+    {
+        /* Make sure HighestAddress is not too large */
+        HighestAddress = min(HighestAddress, (ULONG_PTR)MM_HIGHEST_VAD_ADDRESS);
+
+        /* Which way should we search? */
+        if ((AllocationType & MEM_TOP_DOWN) || CurrentProcess->VmTopDown)
+        {
+            /* Find an address top-down */
+            Result = MiFindEmptyAddressRangeDownTree(ViewSize,
+                                                     HighestAddress,
+                                                     Alignment,
+                                                     &CurrentProcess->VadRoot,
+                                                     &StartingAddress,
+                                                     &Parent);
+        }
+        else
+        {
+            /* Find an address bottom-up */
+            Result = MiFindEmptyAddressRangeInTree(ViewSize,
+                                                   Alignment,
+                                                   &CurrentProcess->VadRoot,
+                                                   &Parent,
+                                                   &StartingAddress);
+        }
+
+        /* Get the ending address, which is the last piece we need for the VAD */
+        EndingAddress = StartingAddress + ViewSize - 1;
+
+        /* Check if we found a suitable location */
+        if ((Result == TableFoundNode) || (EndingAddress > HighestAddress))
+        {
+            DPRINT1("Not enough free space to insert this VAD node!\n");
+            KeReleaseGuardedMutex(&CurrentProcess->AddressCreationLock);
+            return STATUS_CONFLICTING_ADDRESSES;
+        }
+
+        ASSERT(StartingAddress != 0);
+        ASSERT(StartingAddress < (ULONG_PTR)HighestAddress);
+        ASSERT(EndingAddress > StartingAddress);
+    }
+    else
+    {
+        /* Calculate the starting and ending address */
+        StartingAddress = ALIGN_DOWN_BY(*BaseAddress, Alignment);
+        EndingAddress = StartingAddress + ViewSize - 1;
+
+        /* Make sure it doesn't conflict with an existing allocation */
+        Result = MiCheckForConflictingNode(StartingAddress >> PAGE_SHIFT,
+                                           EndingAddress >> PAGE_SHIFT,
+                                           &CurrentProcess->VadRoot,
+                                           &Parent);
+        if (Result == TableFoundNode)
+        {
+            DPRINT1("Given address conflicts with existing node\n");
+            KeReleaseGuardedMutex(&CurrentProcess->AddressCreationLock);
+            return STATUS_CONFLICTING_ADDRESSES;
+        }
+    }
+
+    /* Now set the VAD address */
+    Vad->StartingVpn = StartingAddress >> PAGE_SHIFT;
+    Vad->EndingVpn = EndingAddress >> PAGE_SHIFT;
+
+    /* Check if the VAD is to be secured */
+    if (Vad->u2.VadFlags2.OneSecured)
+    {
+        /* This *must* be a long VAD! */
+        ASSERT(Vad->u2.VadFlags2.LongVad);
+
+        /* Yeah this is retarded, I didn't invent it! */
+        ((PMMVAD_LONG)Vad)->u3.Secured.StartVpn = StartingAddress;
+        ((PMMVAD_LONG)Vad)->u3.Secured.EndVpn = EndingAddress;
+    }
+
+    /* Lock the working set */
+    CurrentThread = PsGetCurrentThread();
+    MiLockProcessWorkingSetUnsafe(CurrentProcess, CurrentThread);
+
+    /* Insert the VAD */
+    CurrentProcess->VadRoot.NodeHint = Vad;
+    MiInsertNode(&CurrentProcess->VadRoot, (PVOID)Vad, Parent, Result);
+
+    /* Release the working set */
+    MiUnlockProcessWorkingSetUnsafe(CurrentProcess, CurrentThread);
+
+    /* Update the process' virtual size, and peak virtual size */
+    CurrentProcess->VirtualSize += ViewSize;
+    if (CurrentProcess->VirtualSize > CurrentProcess->PeakVirtualSize)
+    {
+        CurrentProcess->PeakVirtualSize = CurrentProcess->VirtualSize;
+    }
+
+    /* Unlock the address space */
+    KeReleaseGuardedMutex(&CurrentProcess->AddressCreationLock);
+
+    *BaseAddress = StartingAddress;
+    return STATUS_SUCCESS;
+}
+
 VOID
 NTAPI
 MiInsertBasedSection(IN PSECTION Section)

Modified: trunk/reactos/ntoskrnl/mm/ARM3/virtual.c
URL: http://svn.reactos.org/svn/reactos/trunk/reactos/ntoskrnl/mm/ARM3/virtual.c?rev=64589&r1=64588&r2=64589&view=diff
==============================================================================
--- trunk/reactos/ntoskrnl/mm/ARM3/virtual.c    [iso-8859-1] (original)
+++ trunk/reactos/ntoskrnl/mm/ARM3/virtual.c    [iso-8859-1] Wed Oct  8 00:31:17 2014
@@ -4304,12 +4304,12 @@
 {
     PEPROCESS Process;
     PMEMORY_AREA MemoryArea;
-    PFN_NUMBER PageCount;
     PMMVAD Vad = NULL, FoundVad;
     NTSTATUS Status;
     PMMSUPPORT AddressSpace;
     PVOID PBaseAddress;
-    ULONG_PTR PRegionSize, StartingAddress, EndingAddress, HighestAddress;
+    ULONG_PTR PRegionSize, StartingAddress, EndingAddress;
+    ULONG_PTR HighestAddress = (ULONG_PTR)MM_HIGHEST_VAD_ADDRESS;
     PEPROCESS CurrentProcess = PsGetCurrentProcess();
     KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
     PETHREAD CurrentThread = PsGetCurrentThread();
@@ -4319,7 +4319,6 @@
     MMPTE TempPte;
     PMMPTE PointerPte, PointerPde, LastPte;
     TABLE_SEARCH_RESULT Result;
-    PMMADDRESS_NODE Parent;
     PAGED_CODE();

     /* Check for valid Zero bits */
@@ -4544,7 +4543,6 @@
             // This is a blind commit, all we need is the region size
             //
             PRegionSize = ROUND_TO_PAGES(PRegionSize);
-            PageCount = BYTES_TO_PAGES(PRegionSize);
             EndingAddress = 0;
             StartingAddress = 0;

@@ -4563,13 +4561,6 @@
                     goto FailPathNoLock;
                 }
             }
-            else
-            {
-                //
-                // Use the highest VAD address as maximum
-                //
-                HighestAddress = (ULONG_PTR)MM_HIGHEST_VAD_ADDRESS;
-            }
         }
         else
         {
@@ -4579,8 +4570,8 @@
             // fall based on the aligned address and the passed in region size
             //
             EndingAddress = ((ULONG_PTR)PBaseAddress + PRegionSize - 1) | (PAGE_SIZE - 1);
-            StartingAddress = ROUND_DOWN((ULONG_PTR)PBaseAddress, _64K);
-            PageCount = BYTES_TO_PAGES(EndingAddress - StartingAddress);
+            PRegionSize = EndingAddress + 1 - ROUND_DOWN((ULONG_PTR)PBaseAddress, _64K);
+            StartingAddress = (ULONG_PTR)PBaseAddress;
         }

         //
@@ -4594,7 +4585,7 @@
             goto FailPathNoLock;
         }

-        Vad->u.LongFlags = 0;
+        RtlZeroMemory(Vad, sizeof(MMVAD_LONG));
         if (AllocationType & MEM_COMMIT) Vad->u.VadFlags.MemCommit = 1;
         Vad->u.VadFlags.Protection = ProtectionMask;
         Vad->u.VadFlags.PrivateMemory = 1;
@@ -4602,124 +4593,24 @@
         Vad->ControlArea = NULL; // For Memory-Area hack

         //
-        // Lock the address space and make sure the process isn't already dead
-        //
-        AddressSpace = MmGetCurrentAddressSpace();
-        MmLockAddressSpace(AddressSpace);
-        if (Process->VmDeleted)
-        {
-            Status = STATUS_PROCESS_IS_TERMINATING;
-            goto FailPath;
-        }
-
-        //
-        // Did we have a base address? If no, find a valid address that is 64KB
-        // aligned in the VAD tree. Otherwise, make sure that the address range
-        // which was passed in isn't already conflicting with an existing address
-        // range.
-        //
-        if (!PBaseAddress)
-        {
-            /* Which way should we search? */
-            if ((AllocationType & MEM_TOP_DOWN) || Process->VmTopDown)
-            {
-                /* Find an address top-down */
-                Result = MiFindEmptyAddressRangeDownTree(PRegionSize,
-                                                         HighestAddress,
-                                                         _64K,
-                                                         &Process->VadRoot,
-                                                         &StartingAddress,
-                                                         &Parent);
-            }
-            else
-            {
-                /* Find an address bottom-up */
-                Result = MiFindEmptyAddressRangeInTree(PRegionSize,
-                                                       _64K,
-                                                       &Process->VadRoot,
-                                                       &Parent,
-                                                       &StartingAddress);
-            }
-
-            if (Result == TableFoundNode)
-            {
-                Status = STATUS_NO_MEMORY;
-                goto FailPath;
-            }
-
-            //
-            // Now we know where the allocation ends. Make sure it doesn't end up
-            // somewhere in kernel mode.
-            //
-            ASSERT(StartingAddress != 0);
-            ASSERT(StartingAddress < (ULONG_PTR)MM_HIGHEST_USER_ADDRESS);
-            EndingAddress = (StartingAddress + PRegionSize - 1) | (PAGE_SIZE - 1);
-            ASSERT(EndingAddress > StartingAddress);
-            if (EndingAddress > HighestAddress)
-            {
-                Status = STATUS_NO_MEMORY;
-                goto FailPath;
-            }
-        }
-        else
-        {
-            /* Make sure it doesn't conflict with an existing allocation */
-            Result = MiCheckForConflictingNode(StartingAddress >> PAGE_SHIFT,
-                                               EndingAddress >> PAGE_SHIFT,
-                                               &Process->VadRoot,
-                                               &Parent);
-            if (Result == TableFoundNode)
-            {
-                //
-                // The address specified is in conflict!
-                //
-                Status = STATUS_CONFLICTING_ADDRESSES;
-                goto FailPath;
-            }
-        }
-
-        //
-        // Write out the VAD fields for this allocation
-        //
-        Vad->StartingVpn = StartingAddress >> PAGE_SHIFT;
-        Vad->EndingVpn = EndingAddress >> PAGE_SHIFT;
-
-        //
-        // FIXME: Should setup VAD bitmap
-        //
-        Status = STATUS_SUCCESS;
-
-        //
-        // Lock the working set and insert the VAD into the process VAD tree
-        //
-        MiLockProcessWorkingSetUnsafe(Process, CurrentThread);
-        Process->VadRoot.NodeHint = Vad;
-        MiInsertNode(&Process->VadRoot, (PVOID)Vad, Parent, Result);
-        MiUnlockProcessWorkingSetUnsafe(Process, CurrentThread);
-
-        //
-        // Make sure the actual region size is at least as big as the
-        // requested region size and update the value
-        //
-        ASSERT(PRegionSize <= (EndingAddress + 1 - StartingAddress));
-        PRegionSize = (EndingAddress + 1 - StartingAddress);
-
-        //
-        // Update the virtual size of the process, and if this is now the highest
-        // virtual size we have ever seen, update the peak virtual size to reflect
-        // this.
-        //
-        Process->VirtualSize += PRegionSize;
-        if (Process->VirtualSize > Process->PeakVirtualSize)
-        {
-            Process->PeakVirtualSize = Process->VirtualSize;
-        }
-
-        //
-        // Release address space and detach and dereference the target process if
+        // Insert the VAD
+        //
+        Status = MiInsertVadEx(Vad,
+                               &StartingAddress,
+                               PRegionSize,
+                               HighestAddress,
+                               MM_VIRTMEM_GRANULARITY,
+                               AllocationType);
+        if (!NT_SUCCESS(Status))
+        {
+            DPRINT1("Failed to insert the VAD!\n");
+            goto FailPathNoLock;
+        }
+
+        //
+        // Detach and dereference the target process if
         // it was different from the current process
         //
-        MmUnlockAddressSpace(AddressSpace);
         if (Attached) KeUnstackDetachProcess(&ApcState);
         if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);






_______________________________________________
Ros-dev mailing list
Ros-dev@reactos.org
http://www.reactos.org/mailman/listinfo/ros-dev