Duplicates in /proc/partitions
Corinna Vinschen
corinna-cygwin@cygwin.com
Thu Aug 19 14:48:30 GMT 2021
Hi Anton,
On Aug 19 13:26, Corinna Vinschen via Cygwin wrote:
> On Aug 19 12:03, Corinna Vinschen via Cygwin wrote:
> > On Aug 18 18:36, Lavrentiev, Anton (NIH/NLM/NCBI) [C] via Cygwin wrote:
> > > > And I just confirmed that if I print the context right before NtOpenFile(), and just do
> > > > the "continue",
> > >
> > > But then I also tried this: I removed the "continue" before "NtOpenFile()" and allowed it to proceed,
> > > but I moved NtClose(devhdl) right after the "printf" statement (that we were tweaking), and
> > > inserted the "continue" there, so it does not proceed with enumerating of the partitions.
> > > And so again, I got the output that matches my disks without the duplication. So this actually
> > > exonerates NtOpenFile() :-)
> > > [...]
> >
> > Unfortunately I can't reproduce the issue. I created a couple of
> > virtual drives and added them to my W10 and my W7 VM, but to no avail.
>
> Never mind. I now can reproduce the problem at will. The trick is to
> create objects inside the \Device directory while the loop iterating
> over the directory is running.
>
> As shitty as it is, a NtOpenDirectoryObject/NtQueryDirectoryObject/NtClose
> loop is not working atomically. If a new object is inserted into the
> dir preceeding the currently handled entry (which, on a reliable system
> should *never* occur), the entry is moved by one, and the next
> NtQueryDirectoryObject call returns the same object again.
>
> Windows never disappoints when one is looking for really ugly problems.
>
> The good news is that NtQueryDirectoryObject is also capable to take a
> big buffer and return as much entries as fit in in the buffer. I'll
> give it a try now in the vain hope that this way to call
> NtQueryDirectoryObject returns reliable, atomic-like results...
I tested this on both of my VMs (W7, W10) and the result looks promising.
I implemented this method in the Cygwin DLL locally, for all places
utilizing the NtQueryDirectoryObject call, except for one call where
this isn't possible, unfortunately. That's readdir on /proc/sys
directories, which is supposed to return one entry on each invocation
only and is thus stateful.
Funny enough this problem may also explain why ps(1) output sometimes
showed some Cygwin processes twice. Same method, same problem.
Anyway, would you mind to test the below new proc_partition.c as well
as the latest snapshot I just uploaded to https://cygwin.com/snapshots/?
Thanks,
Corinna
-------------- next part --------------
#include <stdio.h>
#include <windows.h>
#include <winioctl.h>
#include <winternl.h>
#include <wchar.h>
#include <wctype.h>
#include <ntstatus.h>
NTSTATUS NTAPI NtOpenDirectoryObject (PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES);
NTSTATUS NTAPI NtQueryDirectoryObject (HANDLE, PVOID, ULONG, BOOLEAN,
BOOLEAN, PULONG, PULONG);
typedef struct _DIRECTORY_BASIC_INFORMATION
{
UNICODE_STRING ObjectName;
UNICODE_STRING ObjectTypeName;
} DIRECTORY_BASIC_INFORMATION, *PDIRECTORY_BASIC_INFORMATION;
#define DIRECTORY_QUERY 1
#define NT_MAX_PATH 32768
char buf[NT_MAX_PATH];
char ioctl_buf[NT_MAX_PATH];
WCHAR mp_buf[NT_MAX_PATH];
typedef enum { false, true } bool;
int
main ()
{
OBJECT_ATTRIBUTES attr;
IO_STATUS_BLOCK io;
NTSTATUS status;
HANDLE dirhdl;
WCHAR fpath[MAX_PATH];
WCHAR gpath[MAX_PATH];
DWORD len;
/* Note that device ids and names are just faked here, not using the internal
Cygwin code to generate device major/minor numbers and device names.
There's *no* guarantee that the device name and the device major number
is the same as in Cygwin. */
char dev_name = '`';
/* Open \Device object directory. */
wchar_t wpath[MAX_PATH] = L"\\Device";
UNICODE_STRING upath = {14, 16, wpath};
InitializeObjectAttributes (&attr, &upath, OBJ_CASE_INSENSITIVE, NULL, NULL);
status = NtOpenDirectoryObject (&dirhdl, DIRECTORY_QUERY, &attr);
if (!NT_SUCCESS (status))
{
fprintf (stderr, "NtOpenDirectoryObject, status 0x%08x", status);
return 1;
}
/* Traverse \Device directory ... */
PDIRECTORY_BASIC_INFORMATION dbi_buf = (PDIRECTORY_BASIC_INFORMATION)
alloca (65536);
PDIRECTORY_BASIC_INFORMATION dbi = dbi_buf;
BOOLEAN restart = TRUE;
bool got_one = false;
bool last_run = false;
ULONG context = 0;
ULONG bytes_read = 0;
while (!last_run)
{
status = NtQueryDirectoryObject (dirhdl, dbi_buf, 65536, FALSE, restart,
&context, &bytes_read);
if (!NT_SUCCESS (status))
{
fprintf (stderr, "NtQueryDirectoryObject(), status 0x%08x", status);
return 1;
}
if (status != STATUS_MORE_ENTRIES)
last_run = true;
restart = FALSE;
printf ("bytes_read = %lu, context = %lu, status = 0x%08x\n",
(unsigned long) bytes_read, (unsigned long) context,
status);
for (dbi = dbi_buf; dbi->ObjectName.Length > 0; dbi++)
{
HANDLE devhdl;
PARTITION_INFORMATION_EX *pix = NULL;
PARTITION_INFORMATION *pi = NULL;
DWORD bytes_read;
DWORD part_cnt = 0;
unsigned long long size;
/* ... and check for a "Harddisk[0-9]*" entry. */
if (dbi->ObjectName.Length < 9 * sizeof (WCHAR)
|| wcsncasecmp (dbi->ObjectName.Buffer, L"Harddisk", 8) != 0
|| !iswdigit (dbi->ObjectName.Buffer[8]))
continue;
/* Got it. Now construct the path to the entire disk, which is
"\\Device\\HarddiskX\\Partition0", and open the disk with
minimum permissions. */
//unsigned long drive_num = wcstoul (dbi->ObjectName.Buffer + 8, NULL, 10);
wcscpy (wpath, dbi->ObjectName.Buffer);
PWCHAR wpart = wpath + dbi->ObjectName.Length / sizeof (WCHAR);
wcpcpy (wpart, L"\\Partition0");
upath.Length = dbi->ObjectName.Length + 22;
upath.MaximumLength = upath.Length + sizeof (WCHAR);
InitializeObjectAttributes (&attr, &upath, OBJ_CASE_INSENSITIVE,
dirhdl, NULL);
status = NtOpenFile (&devhdl, READ_CONTROL, &attr, &io,
FILE_SHARE_VALID_FLAGS, 0);
if (!NT_SUCCESS (status))
{
fprintf (stderr, "NtOpenFile(%ls), status 0x%08x",
upath.Buffer, status);
continue;
}
if (!got_one)
{
printf ("major minor #blocks name win-mounts\n\n");
got_one = true;
}
/* Fetch partition info for the entire disk to get its size. */
if (DeviceIoControl (devhdl, IOCTL_DISK_GET_PARTITION_INFO_EX, NULL, 0,
ioctl_buf, NT_MAX_PATH, &bytes_read, NULL))
{
pix = (PARTITION_INFORMATION_EX *) ioctl_buf;
size = pix->PartitionLength.QuadPart;
}
else if (DeviceIoControl (devhdl, IOCTL_DISK_GET_PARTITION_INFO, NULL, 0,
ioctl_buf, NT_MAX_PATH, &bytes_read, NULL))
{
pi = (PARTITION_INFORMATION *) ioctl_buf;
size = pi->PartitionLength.QuadPart;
}
else
{
fprintf (stderr, "DeviceIoControl (%ls, "
"IOCTL_DISK_GET_PARTITION_INFO{_EX}) %lu",
upath.Buffer, (unsigned long) GetLastError ());
size = 0;
}
//device dev (drive_num, 0);
++dev_name;
printf ("%5d %5d %9llu sd%c (%lu, %ls)\n",
8, (dev_name - 'a') * 16, size >> 10, dev_name,
(unsigned long) context, dbi->ObjectName.Buffer);
/* Fetch drive layout info to get size of all partitions on the disk. */
if (DeviceIoControl (devhdl, IOCTL_DISK_GET_DRIVE_LAYOUT_EX,
NULL, 0, ioctl_buf, NT_MAX_PATH, &bytes_read, NULL))
{
PDRIVE_LAYOUT_INFORMATION_EX pdlix = (PDRIVE_LAYOUT_INFORMATION_EX)
ioctl_buf;
part_cnt = pdlix->PartitionCount;
pix = pdlix->PartitionEntry;
}
else if (DeviceIoControl (devhdl, IOCTL_DISK_GET_DRIVE_LAYOUT,
NULL, 0, ioctl_buf, NT_MAX_PATH, &bytes_read, NULL))
{
PDRIVE_LAYOUT_INFORMATION pdli = (PDRIVE_LAYOUT_INFORMATION) ioctl_buf;
part_cnt = pdli->PartitionCount;
pi = pdli->PartitionEntry;
}
else
fprintf (stderr, "DeviceIoControl(%ls, "
"IOCTL_DISK_GET_DRIVE_LAYOUT{_EX}): %lu",
upath.Buffer, (unsigned long) GetLastError ());
/* Loop over partitions. */
if (pix || pi)
for (DWORD i = 0; i < part_cnt && i < 64; ++i)
{
DWORD part_num;
if (pix)
{
size = pix->PartitionLength.QuadPart;
part_num = pix->PartitionNumber;
++pix;
}
else
{
size = pi->PartitionLength.QuadPart;
part_num = pi->PartitionNumber;
++pi;
}
/* A partition number of 0 denotes an extended partition or a
filler entry as described in fhandler_dev_floppy::lock_partition.
Just skip. */
if (part_num == 0)
continue;
//device dev (drive_num, part_num);
printf ("%5d %5d %9llu sd%c%d ",
8, (dev_name - 'a') * 16 + part_num, size >> 10,
dev_name, part_num);
/* Check if the partition is mounted in Windows and, if so,
print the mount point list. */
swprintf (fpath, sizeof fpath,
L"\\\\?\\GLOBALROOT\\Device\\%ls\\Partition%u\\",
dbi->ObjectName.Buffer, part_num);
if (GetVolumeNameForVolumeMountPointW (fpath, gpath, MAX_PATH)
&& GetVolumePathNamesForVolumeNameW (gpath, mp_buf,
NT_MAX_PATH, &len))
{
for (PWCHAR p = mp_buf; *p; p = wcschr (p, L'\0') + 1)
printf (" %ls", p);
}
puts ("");
}
NtClose (devhdl);
}
}
NtClose (dirhdl);
if (!got_one)
return 1;
return 0;
}
More information about the Cygwin
mailing list