Christian Franke wrote:
This is a first attempt to partly emulate the Linux directory /dev/disk/by-id. Useful to make sure the correct device is accessed in conjunction with dd, ddrescue, fdisk, ....

Attached is the second attempt.


The additional '*-partN' links to partitions are not yet included.

These are now included.


This only works properly if Win32 path '\\.\PhysicalDriveN' is always trivially mapped to NT path '\Device\HarddiskN\Partition0'. IOCTL_STORAGE_QUERY_PROPERTY with a handle from NtOpenFile(., READ_CONTROL,...) instead of CreateFile(., 0, ...) did not work with all drivers. With stornvme.sys, it fails with permission denied. Perhaps other permission bits are required for NtOpenFile(). Thanks for any info regarding this.

According to NtQueryObject(., ObjectBasicInformation, ...), using NtOpenFile(., MAXIMUM_ALLOWED, ...) without admin rights sets GrantedAccess to 0x001200a0 (FILE_EXECUTE|FILE_READ_ATTRIBUTES|READ_CONTROL|SYNCHRONIZE). For some unknown reason, NVMe drives behind stornvme.sys additionally require SYNCHRONIZE to use IOCTL_STORAGE_QUERY_PROPERTY. Possibly a harmless bug in the access check somewhere in the NVMe stack.

The disk scanning from the first patch has been reworked based on code borrowed from proc.cc:format_proc_partitions(). For the longer term, it may make sense to provide one flexible scanning function for /dev/sdXN, /proc/partitions and /proc/disk/by-id.


Note that the BusType information is often not accurate. For example drives behind Intel RST drivers may always be listed as "raid-PRODUCT_SERIAL" even if not part of a RAID volume.

The changes for the generated file devices.cc are not included in the patch.


--
Regards,
Christian

From ba1990a43b7585f88a9369f5db0bdc56d6e913bf Mon Sep 17 00:00:00 2001
From: Christian Franke <christian.fra...@t-online.de>
Date: Tue, 3 Oct 2023 14:15:12 +0200
Subject: [PATCH] Cygwin: Add /dev/disk/by-id symlinks

The new directory '/dev/disk/by-id' provides symlinks for each
disk and its partitions:
'BUSTYPE-[VENDOR_]PRODUCT_SERIAL[-partN]' -> '../../sdX[N]'.
IOCTL_STORAGE_QUERY_PROPERTY is used to fetch bus type, vendor,
product and serial number information.  Administrator privileges
are not required.

Signed-off-by: Christian Franke <christian.fra...@t-online.de>
---
 winsup/cygwin/Makefile.am               |   1 +
 winsup/cygwin/devices.in                |   4 +
 winsup/cygwin/dtable.cc                 |   3 +
 winsup/cygwin/fhandler/dev_disk.cc      | 589 ++++++++++++++++++++++++
 winsup/cygwin/local_includes/devices.h  |   6 +-
 winsup/cygwin/local_includes/fhandler.h |  47 ++
 winsup/cygwin/mount.cc                  |  10 +
 7 files changed, 659 insertions(+), 1 deletion(-)
 create mode 100644 winsup/cygwin/fhandler/dev_disk.cc

diff --git a/winsup/cygwin/Makefile.am b/winsup/cygwin/Makefile.am
index 64b252a22..376c79fc3 100644
--- a/winsup/cygwin/Makefile.am
+++ b/winsup/cygwin/Makefile.am
@@ -84,6 +84,7 @@ FHANDLER_FILES= \
        fhandler/console.cc \
        fhandler/cygdrive.cc \
        fhandler/dev.cc \
+       fhandler/dev_disk.cc \
        fhandler/dev_fd.cc \
        fhandler/disk_file.cc \
        fhandler/dsp.cc \
diff --git a/winsup/cygwin/devices.in b/winsup/cygwin/devices.in
index 2545dd85e..48d3843fe 100644
--- a/winsup/cygwin/devices.in
+++ b/winsup/cygwin/devices.in
@@ -115,6 +115,9 @@ const _device dev_cygdrive_storage =
 const _device dev_fs_storage =
   {"", {FH_FS}, "", exists};
 
+const _device dev_dev_disk_storage =
+  {"", {FH_DEV_DISK}, "", exists};
+
 const _device dev_proc_storage =
   {"", {FH_PROC}, "", exists};
 
@@ -173,6 +176,7 @@ const _device dev_error_storage =
    the POSIX namespace.  */
 %%
 "/dev", BRACK(FH_DEV), "", exists, S_IFDIR
+"/dev/disk", BRACK(FH_DEV_DISK), "", exists, S_IFDIR
 "/dev/tty", BRACK(FH_TTY), "/dev/tty", exists, S_IFCHR
 "/dev/pty%(0-127)d", BRACK(FHDEV(DEV_PTYS_MAJOR, {$1})), "/dev/pty{$1}", 
exists_pty, S_IFCHR, =ptys_dev
 ":ptym%(0-127)d", BRACK(FHDEV(DEV_PTYM_MAJOR, {$1})), "/dev/ptym{$1}", 
exists_internal, S_IFCHR, =ptym_dev
diff --git a/winsup/cygwin/dtable.cc b/winsup/cygwin/dtable.cc
index 21d525389..9508f3e0b 100644
--- a/winsup/cygwin/dtable.cc
+++ b/winsup/cygwin/dtable.cc
@@ -585,6 +585,9 @@ fh_alloc (path_conv& pc)
        case FH_DEV:
          fh = cnew (fhandler_dev);
          break;
+       case FH_DEV_DISK:
+         fh = cnew (fhandler_dev_disk);
+         break;
        case FH_DEV_FD:
          fh = cnew (fhandler_dev_fd);
          break;
diff --git a/winsup/cygwin/fhandler/dev_disk.cc 
b/winsup/cygwin/fhandler/dev_disk.cc
new file mode 100644
index 000000000..caca57d63
--- /dev/null
+++ b/winsup/cygwin/fhandler/dev_disk.cc
@@ -0,0 +1,589 @@
+/* fhandler/dev_disk.cc: fhandler for the /dev/disk/by-id/... symlinks.
+
+This file is part of Cygwin.
+
+This software is a copyrighted work licensed under the terms of the
+Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
+details. */
+
+#include "winsup.h"
+#include "path.h"
+#include "fhandler.h"
+#include "tls_pbuf.h"
+#include <wctype.h>
+#include <winioctl.h>
+
+/* Replace non-printing and unexpected characters, remove trailing spaces,
+   return remaining string length. */
+static int
+sanitize_id_string (char *s)
+{
+  int lastspace = -1, i;
+  for (i = 0; s[i]; i++)
+    {
+      char c = s[i];
+      if (c != ' ')
+       lastspace = -1;
+      else if (lastspace < 0)
+       lastspace = i;
+      if (('0' <= c && c <= '9') || c == '.' || c == '-'
+         || ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'))
+       continue;
+      s[i] = '_';
+    }
+  if (lastspace >= 0)
+    s[(i = lastspace)] = '\0';
+  return i;
+}
+
+/* Get ID string from STORAGE_DEVICE_DESCRIPTIOR. */
+static bool
+stordesc_to_id_name (const UNICODE_STRING *upath, char *ioctl_buf,
+                   char (& name)[NAME_MAX + 1])
+{
+  const STORAGE_DEVICE_DESCRIPTOR *desc =
+    reinterpret_cast<const STORAGE_DEVICE_DESCRIPTOR *>(ioctl_buf);
+  /* Ignore drive if information is missing, too short or too long. */
+  int vendor_len = 0, product_len = 0, serial_len = 0;
+  if (desc->VendorIdOffset)
+    vendor_len = sanitize_id_string (ioctl_buf + desc->VendorIdOffset);
+  if (desc->ProductIdOffset)
+    product_len = sanitize_id_string (ioctl_buf + desc->ProductIdOffset);
+  if (desc->SerialNumberOffset)
+    serial_len = sanitize_id_string (ioctl_buf + desc->SerialNumberOffset);
+
+  bool valid = (4 <= vendor_len + product_len && 4 <= serial_len
+               && (20 + vendor_len + 1 + product_len + 1 + serial_len + 10)
+                  < (int) sizeof (name));
+  debug_printf ("%S: '%s' '%s' '%s'%s", upath,
+               (vendor_len ? ioctl_buf + desc->VendorIdOffset : ""),
+               (product_len ? ioctl_buf + desc->ProductIdOffset : ""),
+               (serial_len ? ioctl_buf + desc->SerialNumberOffset : ""),
+               (valid ? "" : " (ignored)"));
+  if (!valid)
+    return false;
+
+  /* Translate bus types. */
+  const char *bus;
+  switch (desc->BusType)
+    {
+      case BusTypeAta:     bus = "ata-"; break;
+      case BusTypeFibre:   bus = "fibre-"; break;
+      case BusTypeNvme:    bus = "nvme-"; break;
+      case BusTypeRAID:    bus = "raid-"; break;
+      case BusTypeSas:     bus = "sas-"; break;
+      case BusTypeSata:    bus = "sata-"; break;
+      case BusTypeScsi:    bus = "scsi-"; break;
+      case BusTypeUsb:     bus = "usb-"; break;
+      case BusTypeVirtual: bus = "virtual-"; break;
+      default: bus = nullptr; break;
+    }
+  if (bus)
+    strcpy (name, bus);
+  else
+    __small_sprintf (name, "bustype0x%02x-", desc->BusType);
+
+  /* Create "BUSTYPE-[VENDOR_]PRODUCT_SERIAL" string. */
+  if (vendor_len)
+    strcat (name, ioctl_buf + desc->VendorIdOffset);
+  if (product_len)
+    {
+      if (vendor_len)
+       strcat (name, "_");
+      strcat (name, ioctl_buf + desc->ProductIdOffset);
+    }
+  strcat (name, "_");
+  strcat (name, ioctl_buf + desc->SerialNumberOffset);
+  return true;
+}
+
+struct by_id_entry
+{
+  char name[NAME_MAX + 1];
+  u_int8_t drive;
+  u_int8_t part;
+};
+
+static int
+by_id_compare_name (const void *a, const void *b)
+{
+  const by_id_entry *ap = reinterpret_cast<const by_id_entry *>(a);
+  const by_id_entry *bp = reinterpret_cast<const by_id_entry *>(b);
+  return strcmp (ap->name, bp->name);
+}
+
+static by_id_entry *
+by_id_realloc (by_id_entry *p, size_t n)
+{
+  void *p2 = realloc(p, n * sizeof (*p));
+  if (!p2)
+    {
+      free (p);
+      return nullptr;
+    }
+  return reinterpret_cast<by_id_entry *>(p2);
+}
+
+/* Create sorted name -> drive mapping table. Must be freed by caller. */
+static int
+get_by_id_table (by_id_entry * &table)
+{
+  table = nullptr;
+
+  /* Open \Device object directory. */
+  wchar_t wpath[MAX_PATH] = L"\\Device";
+  UNICODE_STRING upath = {14, 16, wpath};
+  OBJECT_ATTRIBUTES attr;
+  InitializeObjectAttributes (&attr, &upath, OBJ_CASE_INSENSITIVE, nullptr, 
nullptr);
+  HANDLE dirhdl;
+  NTSTATUS status = NtOpenDirectoryObject (&dirhdl, DIRECTORY_QUERY, &attr);
+  if (!NT_SUCCESS (status))
+    {
+      debug_printf ("NtOpenDirectoryObject, status %y", status);
+      __seterrno_from_nt_status (status);
+      return -1;
+    }
+
+  /* Limit disk and partition numbers like fhandler_dev::readdir (). */
+  const unsigned max_drive_num = 127, max_part_num = 63;
+
+  unsigned alloc_size = 0, table_size = 0;
+  tmp_pathbuf tp;
+  char *ioctl_buf = tp.c_get ();
+  DIRECTORY_BASIC_INFORMATION *dbi_buf =
+      reinterpret_cast<DIRECTORY_BASIC_INFORMATION *>(tp.w_get ());
+
+  /* Traverse \Device directory ... */
+  bool errno_set = false;
+  BOOLEAN restart = TRUE;
+  bool last_run = false;
+  ULONG context = 0;
+  while (!last_run)
+    {
+      status = NtQueryDirectoryObject (dirhdl, dbi_buf, 65536, FALSE, restart,
+                                      &context, nullptr);
+      if (!NT_SUCCESS (status))
+       {
+         __seterrno_from_nt_status (status);
+         errno_set = true;
+         debug_printf ("NtQueryDirectoryObject, status %y", status);
+         break;
+       }
+      if (status != STATUS_MORE_ENTRIES)
+       last_run = true;
+      restart = FALSE;
+      for (const DIRECTORY_BASIC_INFORMATION *dbi = dbi_buf;
+          dbi->ObjectName.Length > 0;
+          dbi++)
+       {
+         /* ... 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,
+                                            nullptr, 10);
+         if (drive_num > max_drive_num)
+           continue;
+         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, nullptr);
+         /* SYNCHRONIZE access is required for IOCTL_STORAGE_QUERY_PROPERTY
+            for drives behind some drivers (nvmestor.sys). */
+         HANDLE devhdl;
+         IO_STATUS_BLOCK io;
+         status = NtOpenFile (&devhdl, READ_CONTROL | SYNCHRONIZE, &attr, &io,
+                              FILE_SHARE_VALID_FLAGS, 0);
+         if (!NT_SUCCESS (status))
+           {
+             __seterrno_from_nt_status (status);
+             errno_set = true;
+             debug_printf ("NtOpenFile(%S), status %y", &upath, status);
+             continue;
+           }
+
+         /* Fetch vendor, product and serial number. */
+         DWORD bytes_read;
+         STORAGE_PROPERTY_QUERY query =
+           { StorageDeviceProperty, PropertyStandardQuery, { 0 } };
+         if (!DeviceIoControl (devhdl, IOCTL_STORAGE_QUERY_PROPERTY,
+                               &query, sizeof (query),
+                               ioctl_buf, NT_MAX_PATH,
+                               &bytes_read, nullptr))
+           {
+             __seterrno_from_win_error (GetLastError ());
+             errno_set = true;
+             debug_printf ("DeviceIoControl (%S, "
+                           "IOCTL_STORAGE_QUERY_PROPERTY): %E", &upath);
+             continue;
+           }
+
+         /* Add table space for drive, partitions and end marker. */
+         if (alloc_size <= table_size + max_part_num)
+           {
+             alloc_size = table_size + max_part_num + 8;
+             table = by_id_realloc (table, alloc_size);
+             if (!table)
+               return -1;
+           }
+
+         if (!stordesc_to_id_name (&upath, ioctl_buf, table[table_size].name))
+           continue;
+         int drive_index = table_size++;
+         size_t drive_len = strlen(table[drive_index].name);
+         table[drive_index].drive = drive_num;
+         table[drive_index].part = 0;
+
+         /* Fetch drive layout info to get size of all partitions on disk. */
+         DWORD part_cnt = 0;
+         PARTITION_INFORMATION_EX *pix = nullptr;
+         PARTITION_INFORMATION *pi = nullptr;
+         if (DeviceIoControl (devhdl, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, nullptr, 
0,
+                              ioctl_buf, NT_MAX_PATH, &bytes_read, nullptr))
+           {
+             DRIVE_LAYOUT_INFORMATION_EX *pdlix =
+                 reinterpret_cast<DRIVE_LAYOUT_INFORMATION_EX *>(ioctl_buf);
+             part_cnt = pdlix->PartitionCount;
+             pix = pdlix->PartitionEntry;
+           }
+         else if (DeviceIoControl (devhdl, IOCTL_DISK_GET_DRIVE_LAYOUT, 
nullptr,
+                                   0, ioctl_buf, NT_MAX_PATH, &bytes_read,
+                                   nullptr))
+           {
+             DRIVE_LAYOUT_INFORMATION *pdli =
+                 reinterpret_cast<DRIVE_LAYOUT_INFORMATION *>(ioctl_buf);
+             part_cnt = pdli->PartitionCount;
+             pi = pdli->PartitionEntry;
+           }
+         else
+           debug_printf ("DeviceIoControl(%S, "
+                         "IOCTL_DISK_GET_DRIVE_LAYOUT{_EX}): %E", &upath);
+
+         /* Loop over partitions. */
+         if (pix || pi)
+           for (DWORD i = 0; i < part_cnt; i++)
+             {
+               DWORD part_num;
+               if (pix)
+                 {
+                   part_num = pix->PartitionNumber;
+                   ++pix;
+                 }
+               else
+                 {
+                   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;
+               if (part_num > max_part_num)
+                 break;
+               table[table_size] = table[drive_index];
+               __small_sprintf(table[table_size].name + drive_len, "-part%u",
+                               part_num);
+               table[table_size].part = part_num;
+               table_size++;
+             }
+         NtClose (devhdl);
+       }
+    }
+  NtClose (dirhdl);
+
+  if (!table_size && table)
+    {
+      free (table);
+      table = nullptr;
+    }
+  if (!table)
+    return (errno_set ? -1 : 0);
+
+  /* Sort by name and remove duplicates. */
+  qsort (table, table_size, sizeof (*table), by_id_compare_name);
+  for (unsigned i = 0; i < table_size; i++)
+    {
+      unsigned j = i + 1;
+      while (j < table_size && !strcmp (table[i].name, table[j].name))
+       j++;
+      if (j == i + 1)
+       continue;
+      /* Duplicate(s) found, remove all entries with this name. */
+      debug_printf ("removing duplicates %d-%d: '%s'", i, j - 1, 
table[i].name);
+      if (j < table_size)
+       memmove (table + i, table + j, (table_size - j) * sizeof (*table));
+      table_size -= j - i;
+      i--;
+    }
+
+  debug_printf ("table_size: %d", table_size);
+  return table_size;
+}
+
+const char dev_disk[] = "/dev/disk";
+const size_t dev_disk_len = sizeof (dev_disk) - 1;
+
+fhandler_dev_disk::fhandler_dev_disk ():
+  fhandler_virtual (),
+  loc (unknown_loc),
+  drive_from_id (-1),
+  part_from_id (0)
+{
+}
+
+void
+fhandler_dev_disk::init_dev_disk ()
+{
+  if (loc != unknown_loc)
+    return;
+
+  static const char by_id[] = "/by-id";
+  const size_t by_id_len = sizeof(by_id) - 1;
+
+  /* Determine location. */
+  const char *path = get_name ();
+  if (!path_prefix_p (dev_disk, path, dev_disk_len, false))
+    loc = invalid_loc; // should not happen
+  else if (!path[dev_disk_len])
+    loc = disk_dir; // "/dev/disk"
+  else if (!path_prefix_p (by_id, path + dev_disk_len, by_id_len, false))
+    loc = invalid_loc; // "/dev/disk/invalid"
+  else if (!path[dev_disk_len + by_id_len])
+    loc = by_id_dir; // "/dev/disk/by-id"
+  else if (strchr (path + dev_disk_len + by_id_len + 1, '/'))
+    loc = invalid_loc; // "/dev/disk/by-id/dir/invalid"
+  else
+    loc = by_id_link; // possible "/dev/disk/by-id/LINK"
+  debug_printf ("'%s': loc %d", path, (int)loc);
+
+  /* Done if "/dev/disk", "/dev/disk/by_id" or invalid. */
+  if (loc != by_id_link)
+    return;
+
+  /* Check whether "/dev/disk/by_id/LINK" exists. */
+  by_id_entry *table;
+  int table_size = get_by_id_table (table);
+  if (!table)
+    {
+      loc = invalid_loc;
+      return;
+    }
+
+  by_id_entry key;
+  strcpy (key.name, path + dev_disk_len + by_id_len + 1);
+  const void *found = bsearch (&key, table, table_size, sizeof (*table),
+                              by_id_compare_name);
+  if (found)
+    {
+      /* Preserve drive and partition numbers for fillbuf (). */
+      const by_id_entry *e = reinterpret_cast<const by_id_entry *>(found);
+      drive_from_id = e->drive;
+      part_from_id = e->part;
+    }
+  else
+    loc = invalid_loc;
+  free (table);
+}
+
+virtual_ftype_t
+fhandler_dev_disk::exists ()
+{
+  debug_printf ("exists (%s)", get_name ());
+  ensure_inited ();
+  switch (loc)
+    {
+      case disk_dir:
+      case by_id_dir:
+       return virt_directory;
+      case by_id_link:
+       return virt_symlink;
+      default:
+       return virt_none;
+    }
+}
+
+int
+fhandler_dev_disk::fstat (struct stat *buf)
+{
+  debug_printf ("fstat (%s)", get_name ());
+  ensure_inited ();
+  if (loc == invalid_loc)
+    {
+      set_errno (ENOENT);
+      return -1;
+    }
+
+  fhandler_base::fstat (buf);
+  buf->st_mode = (loc == by_id_link ? S_IFLNK | S_IWUSR | S_IWGRP | S_IWOTH
+                 : S_IFDIR) | STD_RBITS | STD_XBITS;
+  buf->st_ino = get_ino ();
+  return 0;
+}
+
+static inline by_id_entry **
+dir_id_table (DIR *dir)
+{
+  return reinterpret_cast<by_id_entry **>(&dir->__d_internal);
+}
+
+DIR *
+fhandler_dev_disk::opendir (int fd)
+{
+  ensure_inited ();
+  if (!(loc == disk_dir || loc == by_id_dir))
+    {
+      set_errno (ENOTDIR);
+      return nullptr;
+    }
+
+  by_id_entry *table = nullptr;
+  if (loc == by_id_dir)
+    {
+      int table_size = get_by_id_table (table);
+      if (table_size < 0)
+       return nullptr; /* errno is set. */
+      if (table)
+       {
+         /* Shrink to required table_size. */
+         table = by_id_realloc (table, table_size + 1);
+         if (!table)
+           return nullptr; /* Should not happen. */
+         /* Mark end of table for readdir (). */
+         table[table_size].name[0] = '\0';
+       }
+    }
+
+  DIR *dir = fhandler_virtual::opendir (fd);
+  if (!dir)
+    {
+      free (table);
+      return nullptr;
+    }
+  dir->__flags = dirent_saw_dot | dirent_saw_dot_dot;
+  *dir_id_table (dir) = table;
+  return dir;
+}
+
+int
+fhandler_dev_disk::closedir (DIR *dir)
+{
+  free (*dir_id_table (dir));
+  return fhandler_virtual::closedir (dir);
+}
+
+int
+fhandler_dev_disk::readdir (DIR *dir, dirent *de)
+{
+  int res;
+  if (dir->__d_position < 2)
+    {
+      de->d_name[0] = '.';
+      de->d_name[1] = (dir->__d_position ? '.' : '\0');
+      de->d_name[2] = '\0';
+      de->d_type = DT_DIR;
+      dir->__d_position++;
+      res = 0;
+    }
+  else if (loc == disk_dir && dir->__d_position == 2)
+    {
+      strcpy (de->d_name, "by-id");
+      de->d_type = DT_DIR;
+      dir->__d_position++;
+      res = 0;
+    }
+  else if (loc == by_id_dir && *dir_id_table (dir))
+    {
+      const char *name = (*dir_id_table (dir))[dir->__d_position - 2].name;
+      if (name[0])
+       {
+         strcpy (de->d_name, name);
+         de->d_type = DT_LNK;
+         dir->__d_position++;
+         res = 0;
+       }
+      else
+       res = ENMFILE;
+    }
+  else
+    res = ENMFILE;
+
+  syscall_printf ("%d = readdir(%p, %p) (%s)", res, dir, de,
+                 (!res ? de->d_name : ""));
+  return res;
+}
+
+int
+fhandler_dev_disk::open (int flags, mode_t mode)
+{
+  ensure_inited ();
+  int err = 0;
+  if (!fhandler_virtual::open (flags, mode))
+    err = -1;
+  else if (loc == disk_dir || loc == by_id_dir)
+    {
+      if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+       err = EEXIST;
+      else if (flags & O_WRONLY)
+       err = EISDIR;
+      else
+       diropen = true;
+    }
+  /* else if (loc == by_id_link) { } */ /* should not happen */
+  else /* (loc == invalid_loc) */
+    {
+      if (flags & O_CREAT)
+       err = EROFS;
+      else
+       err = ENOENT;
+    }
+
+  int res;
+  if (!err)
+    {
+      nohandle (true);
+      set_open_status ();
+      res = 1;
+    }
+  else
+    {
+      if (err > 0)
+       set_errno (err);
+      res = 0;
+    }
+
+  syscall_printf ("%d = fhandler_dev_disk::open(%y, 0%o)", res, flags, mode);
+  return res;
+}
+
+bool
+fhandler_dev_disk::fill_filebuf ()
+{
+  debug_printf ("fill_filebuf (%s)", get_name ());
+  ensure_inited ();
+  if (!(loc == by_id_link && drive_from_id >= 0))
+    return false;
+
+  char buf[32];
+  int len;
+  if (drive_from_id + 'a' <= 'z')
+    len = __small_sprintf (buf, "../../sd%c", drive_from_id + 'a');
+  else
+    len = __small_sprintf (buf, "../../sd%c%c",
+                          drive_from_id / ('z' - 'a' + 1) - 1 + 'a',
+                          drive_from_id % ('z' - 'a' + 1) + 'a');
+  if (part_from_id)
+    __small_sprintf (buf + len, "%d", part_from_id);
+
+  if (filebuf)
+    cfree (filebuf);
+  filebuf = cstrdup (buf);
+  return true;
+}
diff --git a/winsup/cygwin/local_includes/devices.h 
b/winsup/cygwin/local_includes/devices.h
index 10035263d..1e035f9d6 100644
--- a/winsup/cygwin/local_includes/devices.h
+++ b/winsup/cygwin/local_includes/devices.h
@@ -71,6 +71,7 @@ enum fh_devices
   FH_DEV     = FHDEV (DEV_VIRTFS_MAJOR, 193),
   FH_CYGDRIVE= FHDEV (DEV_VIRTFS_MAJOR, 192),
   FH_DEV_FD  = FHDEV (DEV_VIRTFS_MAJOR, 191),
+  FH_DEV_DISK= FHDEV (DEV_VIRTFS_MAJOR, 190),
 
   FH_SIGNALFD= FHDEV (DEV_VIRTFS_MAJOR, 13),
   FH_TIMERFD = FHDEV (DEV_VIRTFS_MAJOR, 14),
@@ -415,6 +416,8 @@ extern const _device dev_piper_storage;
 #define piper_dev ((device *) &dev_piper_storage)
 extern const _device dev_pipew_storage;
 #define pipew_dev ((device *) &dev_pipew_storage)
+extern const _device dev_dev_disk_storage;
+#define dev_disk_dev ((device *) &dev_dev_disk_storage)
 extern const _device dev_proc_storage;
 #define proc_dev ((device *) &dev_proc_storage)
 extern const _device dev_dev_storage;
@@ -439,7 +442,8 @@ extern const _device dev_fs_storage;
 #define isprocsys_dev(devn) (devn == FH_PROCSYS)
 
 #define isvirtual_dev(devn) \
-  (isproc_dev (devn) || devn == FH_CYGDRIVE || devn == FH_NETDRIVE || devn == 
FH_DEV_FD)
+  (isproc_dev (devn) || devn == FH_CYGDRIVE || devn == FH_NETDRIVE \
+   || devn == FH_DEV_FD || devn == FH_DEV_DISK)
 
 #define iscons_dev(n) \
   ((device::major ((dev_t) (n)) == DEV_CONS_MAJOR) \
diff --git a/winsup/cygwin/local_includes/fhandler.h 
b/winsup/cygwin/local_includes/fhandler.h
index 212c22344..cd61aadf8 100644
--- a/winsup/cygwin/local_includes/fhandler.h
+++ b/winsup/cygwin/local_includes/fhandler.h
@@ -40,6 +40,8 @@ details. */
 extern const char *windows_device_names[];
 extern struct __cygwin_perfile *perfile_table;
 #define __fmode (*(user_data->fmode_ptr))
+extern const char dev_disk[];
+extern const size_t dev_disk_len;
 extern const char proc[];
 extern const size_t proc_len;
 extern const char procsys[];
@@ -3190,6 +3192,50 @@ class fhandler_procnet: public fhandler_proc
   }
 };
 
+class fhandler_dev_disk: public fhandler_virtual
+{
+  enum dev_disk_location {
+    unknown_loc, invalid_loc, disk_dir, by_id_dir, by_id_link
+  };
+  dev_disk_location loc;
+
+  void init_dev_disk ();
+  void ensure_inited ()
+  {
+    if (loc == unknown_loc)
+      init_dev_disk ();
+  }
+
+  int drive_from_id;
+  int part_from_id;
+
+ public:
+  fhandler_dev_disk ();
+  fhandler_dev_disk (void *) {}
+  virtual_ftype_t exists();
+  DIR *opendir (int fd);
+  int closedir (DIR *);
+  int readdir (DIR *, dirent *);
+  int open (int flags, mode_t mode = 0);
+  int fstat (struct stat *buf);
+  bool fill_filebuf ();
+
+  void copy_from (fhandler_base *x)
+  {
+    pc.free_strings ();
+    *this = *reinterpret_cast<fhandler_dev_disk *> (x);
+    _copy_from_reset_helper ();
+  }
+
+  fhandler_dev_disk *clone (cygheap_types malloc_type = HEAP_FHANDLER)
+  {
+    void *ptr = (void *) ccalloc (malloc_type, 1, sizeof (fhandler_dev_disk));
+    fhandler_dev_disk *fh = new (ptr) fhandler_dev_disk (ptr);
+    fh->copy_from (this);
+    return fh;
+  }
+};
+
 class fhandler_dev_fd: public fhandler_virtual
 {
  public:
@@ -3416,6 +3462,7 @@ typedef union
   char __dev_raw[sizeof (fhandler_dev_raw)];
   char __dev_tape[sizeof (fhandler_dev_tape)];
   char __dev_zero[sizeof (fhandler_dev_zero)];
+  char __dev_disk[sizeof (fhandler_dev_disk)];
   char __dev_fd[sizeof (fhandler_dev_fd)];
   char __disk_file[sizeof (fhandler_disk_file)];
   char __fifo[sizeof (fhandler_fifo)];
diff --git a/winsup/cygwin/mount.cc b/winsup/cygwin/mount.cc
index 36ab042a7..13ace6250 100644
--- a/winsup/cygwin/mount.cc
+++ b/winsup/cygwin/mount.cc
@@ -35,6 +35,9 @@ details. */
    (path[mount_table->cygdrive_len + 1] == '/' || \
     !path[mount_table->cygdrive_len + 1]))
 
+#define isdev_disk(path) \
+  (path_prefix_p (dev_disk, (path), dev_disk_len, false))
+
 #define isproc(path) \
   (path_prefix_p (proc, (path), proc_len, false))
 
@@ -685,6 +688,13 @@ mount_info::conv_to_win32_path (const char *src_path, char 
*dst, device& dev,
       /* Go through chroot check */
       goto out;
     }
+  if (isdev_disk (src_path))
+    {
+      dev = *dev_disk_dev;
+      *flags = 0;
+      strcpy (dst, src_path);
+      goto out;
+    }
   if (isproc (src_path))
     {
       dev = *proc_dev;
-- 
2.39.0

Reply via email to