public inbox for cygwin-patches@cygwin.com
 help / color / mirror / Atom feed
* [PATCH] Cygwin: Add /dev/disk/by-id symlinks
@ 2023-09-25 11:57 Christian Franke
  2023-10-03 12:39 ` Christian Franke
  0 siblings, 1 reply; 23+ messages in thread
From: Christian Franke @ 2023-09-25 11:57 UTC (permalink / raw)
  To: cygwin-patches

[-- Attachment #1: Type: text/plain, Size: 964 bytes --]

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, ....

The additional '*-partN' links to partitions are not yet 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.

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


[-- Attachment #2: 0001-Cygwin-Add-dev-disk-by-id-symlinks.patch --]
[-- Type: text/plain, Size: 17629 bytes --]

From 8a49ae067cfd318746e6fe332b8775011658d780 Mon Sep 17 00:00:00 2001
From: Christian Franke <christian.franke@t-online.de>
Date: Mon, 25 Sep 2023 13:24:40 +0200
Subject: [PATCH] Cygwin: Add /dev/disk/by-id symlinks

The new directory '/dev/disk/by-id' provides symlinks for each
disk based on information from IOCTL_STORAGE_QUERY_PROPERTY:
'BUSTYPE_[VENDOR_]PRODUCT_SERIAL' -> '../../sdX'

Signed-off-by: Christian Franke <christian.franke@t-online.de>
---
 winsup/cygwin/Makefile.am               |   1 +
 winsup/cygwin/devices.in                |   4 +
 winsup/cygwin/dtable.cc                 |   3 +
 winsup/cygwin/fhandler/dev_disk.cc      | 389 ++++++++++++++++++++++++
 winsup/cygwin/local_includes/devices.h  |   6 +-
 winsup/cygwin/local_includes/fhandler.h |  45 +++
 winsup/cygwin/mount.cc                  |  10 +
 7 files changed, 457 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..14b0cdf36
--- /dev/null
+++ b/winsup/cygwin/fhandler/dev_disk.cc
@@ -0,0 +1,389 @@
+/* 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 <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 for drive number. */
+static bool
+get_drive_id_name (u_int8_t drive, char (& name)[NAME_MAX + 1])
+{
+  /* Using CreateFile () because with NtOpenFile (., READ_CONTROL, ...) the
+     IOCTL_STORAGE_QUERY_PROPERTY call fails with ERROR_ACCESS_DENIED for
+     drives behind some drivers (nvmestor.sys). */
+  char path[64];
+  __small_sprintf (path, "\\\\.\\PhysicalDrive%d", drive);
+  HANDLE h = CreateFileA (path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
+			  nullptr, OPEN_EXISTING, 0, nullptr);
+  if (h == INVALID_HANDLE_VALUE)
+    return false;
+
+  STORAGE_PROPERTY_QUERY query =
+    { StorageDeviceProperty, PropertyStandardQuery, { 0 } };
+  union {
+    char raw[1024];
+    STORAGE_DEVICE_DESCRIPTOR desc;
+  } desc_data;
+  DWORD bytes_read;
+  BOOL ok = DeviceIoControl (h, IOCTL_STORAGE_QUERY_PROPERTY,
+			     &query, sizeof (query),
+			     &desc_data, sizeof (desc_data),
+			     &bytes_read, nullptr);
+  CloseHandle (h);
+  if (!ok)
+    {
+      debug_printf ("ignoring drive %d: IOCTL_STORAGE_QUERY_PROPERTY failed: %u",
+		    drive, GetLastError ());
+      return false;
+    }
+
+  /* Ignore drive if information is missing, too short or too long. */
+  int vendor_len = 0, product_len = 0, serial_len = 0;
+  if (desc_data.desc.VendorIdOffset)
+    vendor_len = sanitize_id_string (desc_data.raw + desc_data.desc.VendorIdOffset);
+  if (desc_data.desc.ProductIdOffset)
+    product_len = sanitize_id_string (desc_data.raw + desc_data.desc.ProductIdOffset);
+  if (desc_data.desc.SerialNumberOffset)
+    serial_len = sanitize_id_string (desc_data.raw + desc_data.desc.SerialNumberOffset);
+
+  bool valid = (4 <= vendor_len + product_len && 4 <= serial_len
+		&& vendor_len + 1 + product_len + 1 + serial_len < (int) sizeof (name));
+  debug_printf ("drive %d: '%s' '%s' '%s'%s", drive,
+		(vendor_len ? desc_data.raw + desc_data.desc.VendorIdOffset : ""),
+		(product_len ? desc_data.raw + desc_data.desc.ProductIdOffset : ""),
+		(serial_len ? desc_data.raw + desc_data.desc.SerialNumberOffset : ""),
+		(valid ? "" : " (ignored)"));
+  if (!valid)
+    return false;
+
+  /* Translate bus types. */
+  const char *bus;
+  switch (desc_data.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_data.desc.BusType);
+
+  /* Create "BUSTYPE-[VENDOR_]PRODUCT_SERIAL" string. */
+  if (vendor_len)
+    strcat (name, desc_data.raw + desc_data.desc.VendorIdOffset);
+  if (product_len)
+    {
+      if (vendor_len)
+	strcat (name, "_");
+      strcat (name, desc_data.raw + desc_data.desc.ProductIdOffset);
+    }
+  strcat (name, "_");
+  strcat (name, desc_data.raw + desc_data.desc.SerialNumberOffset);
+  return true;
+}
+
+struct by_id_entry
+{
+  char name[NAME_MAX + 1];
+  u_int8_t drive;
+};
+
+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);
+}
+
+/* Create sorted name -> drive mapping table. Must be freed by caller. */
+static by_id_entry *
+get_by_id_table (int * ret_size = nullptr)
+{
+  /* Scan 128 drives like fhandler_dev::readdir () does. */
+  by_id_entry * table = nullptr;
+  int alloc_size = 0, size = 0;
+  for (u_int8_t drive = 0; drive < 128; drive++)
+    {
+      if (alloc_size <= size)
+	{
+	  alloc_size = size + 8;
+	  by_id_entry * table2 = reinterpret_cast<by_id_entry *>(
+	    realloc (table, alloc_size * sizeof (*table))
+	  );
+	  if (!table2)
+	    {
+	      free (table);
+	      return nullptr;
+	    }
+	  table = table2;
+	}
+
+      if (!get_drive_id_name (drive, table[size].name))
+	continue;
+      table[size].drive = drive;
+      size++;
+    }
+
+  /* Sort by name and remove duplicates. */
+  qsort (table, size, sizeof (*table), by_id_compare_name);
+  for (int i = 0; i < size; i++)
+    {
+      int j = i + 1;
+      while (j < 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 < size)
+	memmove (table + i, table + j, (size - j) * sizeof (*table));
+      size -= j - i;
+      i--;
+    }
+
+  /* Mark end of table for readdir (). */
+  table[size].name[0] = '\0';
+
+  if (ret_size)
+    *ret_size = size;
+  debug_printf ("size: %d\n", size);
+  return table;
+}
+
+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)
+{
+}
+
+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. */
+  int tabsize;
+  by_id_entry *table = get_by_id_table (&tabsize);
+  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, tabsize, sizeof (*table),
+			       by_id_compare_name);
+  if (found)
+    /* Preserve drive number for fillbuf (). */
+    drive_from_id = reinterpret_cast<const by_id_entry *>(found)->drive;
+  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;
+}
+
+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;
+  if (loc == by_id_dir)
+    {
+      table = get_by_id_table ();
+      if (!table)
+	{
+	  set_errno (ENOMEM);
+	  return nullptr;
+	}
+    }
+  else
+    table = nullptr;
+
+  DIR *dir = fhandler_virtual::opendir (fd);
+  if (!dir)
+    {
+      free (table);
+      return nullptr;
+    }
+  dir->__flags = dirent_saw_dot | dirent_saw_dot_dot;
+  dir->__handle = table;
+  return dir;
+}
+
+int
+fhandler_dev_disk::closedir (DIR *dir)
+{
+  free (dir->__handle);
+  return fhandler_virtual::closedir (dir);
+}
+
+int
+fhandler_dev_disk::readdir (DIR *dir, dirent *de)
+{
+  int res;
+  if (dir->__d_position <= 1)
+    {
+      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->__handle)
+    {
+      const by_id_entry *table = reinterpret_cast<const by_id_entry *>(dir->__handle);
+      const char *name = table[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;
+}
+
+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];
+  if (drive_from_id + 'a' <= 'z')
+    __small_sprintf (buf, "../../sd%c", drive_from_id + 'a');
+  else
+    __small_sprintf (buf, "../../sd%c%c",
+		     drive_from_id / ('z' - 'a' + 1) - 1 + 'a',
+		     drive_from_id % ('z' - 'a' + 1) + 'a');
+
+  free (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..486b5f49d 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,48 @@ 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;
+
+ public:
+  fhandler_dev_disk ();
+  fhandler_dev_disk (void *) {}
+  virtual_ftype_t exists();
+  DIR *opendir (int fd);
+  int closedir (DIR *);
+  int readdir (DIR *, dirent *);
+  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 +3460,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


^ permalink raw reply	[flat|nested] 23+ messages in thread

end of thread, other threads:[~2023-11-07 15:23 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-09-25 11:57 [PATCH] Cygwin: Add /dev/disk/by-id symlinks Christian Franke
2023-10-03 12:39 ` Christian Franke
2023-11-03  9:55   ` Corinna Vinschen
2023-11-03 10:09     ` Corinna Vinschen
2023-11-03 11:06       ` Christian Franke
2023-11-03 11:11         ` Corinna Vinschen
2023-11-03 11:10       ` Corinna Vinschen
2023-11-03 13:27         ` Corinna Vinschen
2023-11-03 16:09           ` Christian Franke
2023-11-03 16:27             ` Corinna Vinschen
2023-11-03 16:30               ` Corinna Vinschen
2023-11-03 17:54                 ` Christian Franke
2023-11-04  9:34                   ` Corinna Vinschen
2023-11-04  9:57                     ` Corinna Vinschen
2023-11-04 11:34                       ` Christian Franke
2023-11-04 15:53                       ` Christian Franke
2023-11-04 20:51                         ` Corinna Vinschen
2023-11-05 15:45                           ` Christian Franke
2023-11-05 19:59                             ` Corinna Vinschen
2023-11-07 10:10                               ` Christian Franke
2023-11-07 13:29                                 ` Corinna Vinschen
2023-11-07 14:30                                   ` Christian Franke
2023-11-07 15:23                                     ` Corinna Vinschen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).