Cygwin: pinfo: use stpcpy where appropriate
[newlib-cygwin.git] / winsup / cygwin / fhandler / dev_disk.cc
blob29af9de95a0eced0236fe4b865fbabfda20ef24b
1 /* fhandler/dev_disk.cc: fhandler for the /dev/disk/by-id/... symlinks.
3 This file is part of Cygwin.
5 This software is a copyrighted work licensed under the terms of the
6 Cygwin license. Please consult the file "CYGWIN_LICENSE" for
7 details. */
9 #include "winsup.h"
10 #include "path.h"
11 #include "fhandler.h"
12 #include "tls_pbuf.h"
13 #include <storduid.h>
14 #include <wctype.h>
15 #include <winioctl.h>
17 /* Replace invalid characters. Optionally remove leading and trailing
18 characters. Return remaining string length. */
19 template <typename char_type, typename func_type>
20 static int
21 sanitize_string (char_type *s, char_type leading, char_type trailing,
22 char_type replace, func_type valid)
24 int first = 0;
25 if (leading)
26 while (s[first] == leading)
27 first++;
28 int len = -1, i;
29 for (i = 0; s[first + i]; i++)
31 char_type c = s[first + i];
32 if (c != trailing)
33 len = -1;
34 else if (len < 0)
35 len = i;
36 if (!valid (c))
37 c = replace;
38 else if (!first)
39 continue;
40 s[i] = c;
42 if (len < 0)
43 len = i;
44 s[len] = (char_type) 0;
45 return len;
48 /* Variant for device identify strings. */
49 static int
50 sanitize_id_string (char *s)
52 return sanitize_string (s, ' ', ' ', '_', [] (char c) -> bool
54 return (('0' <= c && c <= '9') || c == '.' || c == '-'
55 || ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'));
60 /* Variant for volume labels. */
61 static int
62 sanitize_label_string (WCHAR *s)
64 /* Linux does not skip leading spaces. */
65 return sanitize_string (s, L'\0', L' ', L'_', [] (WCHAR c) -> bool
67 /* Labels may contain characters not allowed in filenames. Also
68 replace '#' to avoid that duplicate markers introduce new
69 duplicates. Linux replaces spaces with \x20 which is not an
70 option here. */
71 return !(c == L'/' || c == L'\\' || c == L'#');
76 /* Fetch storage properties and create the ID string.
77 returns: 1: success, 0: device ignored, -1: IoControl error. */
78 static int
79 storprop_to_id_name (HANDLE devhdl, const UNICODE_STRING *upath,
80 char *ioctl_buf, char (& name)[NAME_MAX + 1])
82 DWORD bytes_read;
83 STORAGE_PROPERTY_QUERY descquery =
84 { StorageDeviceProperty, PropertyStandardQuery, { 0 } };
85 if (!DeviceIoControl (devhdl, IOCTL_STORAGE_QUERY_PROPERTY,
86 &descquery, sizeof (descquery),
87 ioctl_buf, NT_MAX_PATH,
88 &bytes_read, nullptr))
90 __seterrno_from_win_error (GetLastError ());
91 debug_printf ("DeviceIoControl (%S, IOCTL_STORAGE_QUERY_PROPERTY,"
92 " {StorageDeviceProperty}): %E", upath);
93 return -1;
96 const STORAGE_DEVICE_DESCRIPTOR *desc =
97 reinterpret_cast<const STORAGE_DEVICE_DESCRIPTOR *>(ioctl_buf);
98 int vendor_len = 0, product_len = 0, serial_len = 0;
99 if (desc->VendorIdOffset)
100 vendor_len = sanitize_id_string (ioctl_buf + desc->VendorIdOffset);
101 if (desc->ProductIdOffset)
102 product_len = sanitize_id_string (ioctl_buf + desc->ProductIdOffset);
103 if (desc->SerialNumberOffset)
104 serial_len = sanitize_id_string (ioctl_buf + desc->SerialNumberOffset);
106 /* Ignore drive if information is empty or too long (should not happen). */
107 if (!((vendor_len || product_len) && (20/*bustype*/ + vendor_len + 1 +
108 product_len + 1 + serial_len + 1 + 34/*hash*/ + 1 + 10/*partN*/
109 < (int) sizeof (name))))
110 return 0;
112 /* Translate bus types. */
113 const char *bus;
114 switch (desc->BusType)
116 case BusTypeAta: bus = "ata-"; break;
117 case BusTypeFibre: bus = "fibre-"; break;
118 case BusTypeNvme: bus = "nvme-"; break;
119 case BusTypeRAID: bus = "raid-"; break;
120 case BusTypeSas: bus = "sas-"; break;
121 case BusTypeSata: bus = "sata-"; break;
122 case BusTypeScsi: bus = "scsi-"; break;
123 case BusTypeUsb: bus = "usb-"; break;
124 case BusTypeVirtual: bus = "virtual-"; break;
125 default: bus = nullptr; break;
128 /* Create "BUSTYPE-[VENDOR_]PRODUCT[_SERIAL]" string. */
129 char * cp = name;
130 if (bus)
131 cp = stpcpy (cp, bus);
132 else
133 cp += __small_sprintf (cp, "bustype%02_y-", desc->BusType);
135 if (vendor_len)
136 cp = stpcpy (cp, ioctl_buf + desc->VendorIdOffset);
137 if (product_len)
139 if (vendor_len)
140 cp = stpcpy (cp, "_");
141 cp = stpcpy (cp, ioctl_buf + desc->ProductIdOffset);
143 if (serial_len)
145 cp = stpcpy (cp, "_");
146 cp = stpcpy (cp, ioctl_buf + desc->SerialNumberOffset);
149 /* Add hash if information is too short (e.g. missing serial number). */
150 bool add_hash = !(4 <= vendor_len + product_len && 4 <= serial_len);
151 debug_printf ("%S: bustype: %02_y, add_hash: %d, id: '%s' '%s' '%s' ",
152 upath, (unsigned) desc->BusType, (int) add_hash,
153 (vendor_len ? ioctl_buf + desc->VendorIdOffset : ""),
154 (product_len ? ioctl_buf + desc->ProductIdOffset : ""),
155 (serial_len ? ioctl_buf + desc->SerialNumberOffset : ""));
156 if (!add_hash)
157 return 1;
159 /* The call below also returns the STORAGE_DEVICE_DESCRIPTOR used above.
160 MSDN documentation for STORAGE_DEVICE_UNIQUE_IDENTIFIER says:
161 "The device descriptor contains IDs that are extracted from non-VPD
162 inquiry data." This may mean that the serial number (part of
163 SCSI/SAS VPD data) may not always be provided. Therefore a separate
164 call to retrieve STORAGE_DEVICE_DESCRIPTOR only is done above. */
165 STORAGE_PROPERTY_QUERY idquery =
166 { StorageDeviceUniqueIdProperty, PropertyStandardQuery, { 0 } };
167 if (!DeviceIoControl (devhdl, IOCTL_STORAGE_QUERY_PROPERTY,
168 &idquery, sizeof (idquery),
169 ioctl_buf, NT_MAX_PATH,
170 &bytes_read, nullptr))
172 __seterrno_from_win_error (GetLastError ());
173 debug_printf ("DeviceIoControl (%S, IOCTL_STORAGE_QUERY_PROPERTY,"
174 " {StorageDeviceUniqueIdProperty}): %E", upath);
175 return -1;
178 /* Utilize the DUID as defined by MSDN to generate a hash. */
179 const STORAGE_DEVICE_UNIQUE_IDENTIFIER *id =
180 reinterpret_cast<const STORAGE_DEVICE_UNIQUE_IDENTIFIER *>(ioctl_buf);
181 debug_printf ("STORAGE_DEVICE_UNIQUE_IDENTIFIER.Size: %u", id->Size);
183 __int128 hash = 0;
184 for (ULONG i = 0; i < id->Size; i++)
185 hash = ioctl_buf[i] + (hash << 6) + (hash << 16) - hash;
186 __small_sprintf (cp, "_%016_Y%016_X", (unsigned long long) (hash >> 64),
187 (unsigned long long) hash);
188 return 1;
191 /* ("HarddiskN", PART_NUM) -> "\\\\?\\Volume{GUID}\\" */
192 static bool
193 partition_to_volpath (const UNICODE_STRING *drive_uname, DWORD part_num,
194 WCHAR (& volpath)[MAX_PATH])
196 WCHAR gpath[MAX_PATH];
197 __small_swprintf (gpath, L"\\\\?\\GLOBALROOT\\Device\\%S\\Partition%u\\",
198 drive_uname, part_num);
199 if (!GetVolumeNameForVolumeMountPointW (gpath, volpath, sizeof(volpath)))
201 debug_printf ("GetVolumeNameForVolumeMountPointW(%W): %E", gpath);
202 return false;
204 debug_printf ("%W -> %W", gpath, volpath);
205 return true;
208 /* ("HarddiskN", PART_NUM) -> "x" */
209 static bool
210 partition_to_drive(const UNICODE_STRING *drive_uname, DWORD part_num,
211 WCHAR *w_buf, char *name)
213 WCHAR volpath[MAX_PATH];
214 if (!partition_to_volpath (drive_uname, part_num, volpath))
215 return false;
217 DWORD len;
218 if (!GetVolumePathNamesForVolumeNameW (volpath, w_buf, NT_MAX_PATH, &len))
220 debug_printf ("GetVolumePathNamesForVolumeNameW(%W): %E", volpath);
221 return false;
223 debug_printf ("%W -> '%W'%s", volpath, w_buf,
224 (w_buf[0] && wcschr (w_buf, L'\0')[1] ? ", ..." : ""));
226 /* Find first "X:\\", skip if not found.
227 FIXME: Support multiple drive letters. */
228 WCHAR *p;
229 for (p = w_buf; ; p = wcschr (p, L'\0') + 1)
231 if (!*p)
232 return false;
233 if (L'A' <= p[0] && p[0] <= L'Z' && p[1] == L':' && p[2] == L'\\' && !p[3])
234 break;
236 name[0] = (p[0] - L'A') + 'a';
237 name[1] = '\0';
238 return true;
241 /* ("HarddiskN", PART_NUM) -> "VOLUME_GUID" */
242 static bool
243 partition_to_voluuid(const UNICODE_STRING *drive_uname, DWORD part_num,
244 char *name)
246 WCHAR volpath[MAX_PATH];
247 if (!partition_to_volpath (drive_uname, part_num, volpath))
248 return false;
250 /* Skip if not "\\\\?\\Volume{GUID}...". */
251 static const WCHAR prefix[] = L"\\\\?\\Volume{";
252 const size_t prefix_len = sizeof (prefix) / sizeof(WCHAR) - 1, uuid_len = 36;
253 if (!(!wcsncmp (volpath, prefix, prefix_len)
254 && volpath[prefix_len + uuid_len] == L'}'))
255 return false;
257 /* Extract GUID. */
258 volpath[prefix_len + uuid_len] = 0;
259 __small_sprintf (name, "%W", volpath + prefix_len);
261 if (!strncmp (name + 9, "0000-0000-00", 12) && !strcmp (name + 32, "0000"))
263 /* MBR "GUID": Use same SERIAL-OFFSET format as in by-partuuid directory.
264 SERIAL-0000-0000-009a-785634120000 -> SERIAL-123456789a00 */
265 for (int i = 9, j = 30; i < 19; i += 2, j -= 2)
267 if (j == 22) // name[j + 1] == '-'
268 j--;
269 name[i] = name[j];
270 name[i + 1] = name[j + 1];
272 name[21] = '\0';
274 return true;
277 /* ("HarddiskN", PART_NUM) -> "VOLUME_LABEL" or "VOLUME_SERIAL" */
278 static bool
279 partition_to_label_or_uuid(bool uuid, const UNICODE_STRING *drive_uname,
280 DWORD part_num, char *ioctl_buf, char *name)
282 WCHAR wpath[MAX_PATH];
283 /* Trailing backslash is required. */
284 size_t len = __small_swprintf (wpath, L"\\Device\\%S\\Partition%u\\",
285 drive_uname, part_num);
286 len *= sizeof (WCHAR);
287 UNICODE_STRING upath = {(USHORT) len, (USHORT) (len + 1), wpath};
288 OBJECT_ATTRIBUTES attr;
289 InitializeObjectAttributes (&attr, &upath, OBJ_CASE_INSENSITIVE, nullptr,
290 nullptr);
291 IO_STATUS_BLOCK io;
292 HANDLE volhdl;
293 NTSTATUS status = NtOpenFile (&volhdl, READ_CONTROL, &attr, &io,
294 FILE_SHARE_VALID_FLAGS, 0);
295 if (!NT_SUCCESS (status))
297 /* Fails with STATUS_UNRECOGNIZED_VOLUME (0xC000014F) if the
298 partition/filesystem type is unsupported. */
299 debug_printf ("NtOpenFile(%S), status %y", upath, status);
300 return false;
303 /* Check for possible 64-bit NTFS serial number first. */
304 DWORD bytes_read;
305 const NTFS_VOLUME_DATA_BUFFER *nvdb =
306 reinterpret_cast<const NTFS_VOLUME_DATA_BUFFER *>(ioctl_buf);
307 if (uuid && DeviceIoControl (volhdl, FSCTL_GET_NTFS_VOLUME_DATA, nullptr, 0,
308 ioctl_buf, NT_MAX_PATH, &bytes_read, nullptr))
310 /* Print without any separator as on Linux. */
311 __small_sprintf (name, "%016X", nvdb->VolumeSerialNumber.QuadPart);
312 NtClose(volhdl);
313 return true;
316 /* Get label and 32-bit serial number. */
317 status = NtQueryVolumeInformationFile (volhdl, &io, ioctl_buf,
318 NT_MAX_PATH, FileFsVolumeInformation);
319 NtClose(volhdl);
320 if (!NT_SUCCESS (status))
322 debug_printf ("NtQueryVolumeInformationFile(%S), status %y", upath,
323 status);
324 return false;
327 FILE_FS_VOLUME_INFORMATION *ffvi =
328 reinterpret_cast<FILE_FS_VOLUME_INFORMATION *>(ioctl_buf);
329 if (uuid)
330 /* Print with separator as on Linux. */
331 __small_sprintf (name, "%04x-%04x", ffvi->VolumeSerialNumber >> 16,
332 ffvi->VolumeSerialNumber & 0xffff);
333 else
335 /* Label is not null terminated. */
336 ffvi->VolumeLabel[ffvi->VolumeLabelLength / sizeof(WCHAR)] = L'\0';
337 int len = sanitize_label_string (ffvi->VolumeLabel);
338 if (!len)
339 return false;
340 __small_sprintf (name, "%W", ffvi->VolumeLabel);
342 return true;
345 struct by_id_entry
347 char name[NAME_MAX + 1];
348 u_int8_t drive;
349 u_int8_t part;
352 static int
353 by_id_compare_name (const void *a, const void *b)
355 const by_id_entry *ap = reinterpret_cast<const by_id_entry *>(a);
356 const by_id_entry *bp = reinterpret_cast<const by_id_entry *>(b);
357 return strcmp (ap->name, bp->name);
360 static int
361 by_id_compare_name_drive_part (const void *a, const void *b)
363 const by_id_entry *ap = reinterpret_cast<const by_id_entry *>(a);
364 const by_id_entry *bp = reinterpret_cast<const by_id_entry *>(b);
365 int cmp = strcmp (ap->name, bp->name);
366 if (cmp)
367 return cmp;
368 cmp = ap->drive - bp->drive;
369 if (cmp)
370 return cmp;
371 return ap->part - bp->part;
374 static by_id_entry *
375 by_id_realloc (by_id_entry *p, size_t n)
377 void *p2 = realloc (p, n * sizeof (*p));
378 if (!p2)
379 free (p);
380 return reinterpret_cast<by_id_entry *>(p2);
383 static bool
384 format_partuuid (char *name, const PARTITION_INFORMATION_EX *pix)
386 const GUID *guid;
387 switch (pix->PartitionStyle)
389 case PARTITION_STYLE_MBR: guid = &pix->Mbr.PartitionId; break;
390 case PARTITION_STYLE_GPT: guid = &pix->Gpt.PartitionId; break;
391 default: return false;
394 if (pix->PartitionStyle == PARTITION_STYLE_MBR && !guid->Data2
395 && !guid->Data3 && !guid->Data4[6] && !guid->Data4[7])
396 /* MBR "GUID": SERIAL-0000-0000-00NN-NNNNNNN00000
397 Byte offset in LE order -----^^^^-^^^^^^^
398 Print as: SERIAL-OFFSET */
399 __small_sprintf(name,
400 "%08_x-%02_x%02_x%02_x%02_x%02_x%02_x",
401 guid->Data1, guid->Data4[5], guid->Data4[4], guid->Data4[3],
402 guid->Data4[2], guid->Data4[1], guid->Data4[0]);
403 else
404 __small_sprintf(name,
405 "%08_x-%04_x-%04_x-%02_x%02_x-%02_x%02_x%02_x%02_x%02_x%02_x",
406 guid->Data1, guid->Data2, guid->Data3,
407 guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3],
408 guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);
410 return true;
413 /* Create sorted name -> drive mapping table. Must be freed by caller. */
414 static int
415 get_by_id_table (by_id_entry * &table, fhandler_dev_disk::dev_disk_location loc)
417 table = nullptr;
419 /* Open \Device object directory. */
420 wchar_t wpath[MAX_PATH] = L"\\Device";
421 UNICODE_STRING upath = {14, 16, wpath};
422 OBJECT_ATTRIBUTES attr;
423 InitializeObjectAttributes (&attr, &upath, OBJ_CASE_INSENSITIVE, nullptr, nullptr);
424 HANDLE dirhdl;
425 NTSTATUS status = NtOpenDirectoryObject (&dirhdl, DIRECTORY_QUERY, &attr);
426 if (!NT_SUCCESS (status))
428 debug_printf ("NtOpenDirectoryObject, status %y", status);
429 __seterrno_from_nt_status (status);
430 return -1;
433 /* Limit disk and partition numbers like fhandler_dev::readdir (). */
434 const unsigned max_drive_num = 127, max_part_num = 63;
436 unsigned alloc_size = 0, table_size = 0;
437 tmp_pathbuf tp;
438 char *ioctl_buf = tp.c_get ();
439 char *ioctl_buf2 = (loc == fhandler_dev_disk::disk_by_label
440 || loc == fhandler_dev_disk::disk_by_uuid ?
441 tp.c_get () : nullptr);
442 WCHAR *w_buf = (loc == fhandler_dev_disk::disk_by_drive ?
443 tp.w_get () : nullptr);
444 DIRECTORY_BASIC_INFORMATION *dbi_buf =
445 reinterpret_cast<DIRECTORY_BASIC_INFORMATION *>(tp.w_get ());
447 /* Traverse \Device directory ... */
448 bool errno_set = false;
449 HANDLE devhdl = nullptr;
450 BOOLEAN restart = TRUE;
451 bool last_run = false;
452 ULONG context = 0;
453 while (!last_run)
455 if (devhdl)
457 /* Close handle from previous run. */
458 NtClose(devhdl);
459 devhdl = nullptr;
462 status = NtQueryDirectoryObject (dirhdl, dbi_buf, 65536, FALSE, restart,
463 &context, nullptr);
464 if (!NT_SUCCESS (status))
466 __seterrno_from_nt_status (status);
467 errno_set = true;
468 debug_printf ("NtQueryDirectoryObject, status %y", status);
469 break;
471 if (status != STATUS_MORE_ENTRIES)
472 last_run = true;
473 restart = FALSE;
474 for (const DIRECTORY_BASIC_INFORMATION *dbi = dbi_buf;
475 dbi->ObjectName.Length > 0;
476 dbi++)
478 /* ... and check for a "Harddisk[0-9]*" entry. */
479 if (dbi->ObjectName.Length < 9 * sizeof (WCHAR)
480 || wcsncasecmp (dbi->ObjectName.Buffer, L"Harddisk", 8) != 0
481 || !iswdigit (dbi->ObjectName.Buffer[8]))
482 continue;
483 /* Got it. Now construct the path to the entire disk, which is
484 "\\Device\\HarddiskX\\Partition0", and open the disk with
485 minimum permissions. */
486 unsigned long drive_num = wcstoul (dbi->ObjectName.Buffer + 8,
487 nullptr, 10);
488 if (drive_num > max_drive_num)
489 continue;
490 wcscpy (wpath, dbi->ObjectName.Buffer);
491 PWCHAR wpart = wpath + dbi->ObjectName.Length / sizeof (WCHAR);
492 wcpcpy (wpart, L"\\Partition0");
493 upath.Length = dbi->ObjectName.Length + 22;
494 upath.MaximumLength = upath.Length + sizeof (WCHAR);
495 InitializeObjectAttributes (&attr, &upath, OBJ_CASE_INSENSITIVE,
496 dirhdl, nullptr);
497 /* SYNCHRONIZE access is required for IOCTL_STORAGE_QUERY_PROPERTY
498 for drives behind some drivers (nvmestor.sys). */
499 IO_STATUS_BLOCK io;
500 status = NtOpenFile (&devhdl, READ_CONTROL | SYNCHRONIZE, &attr, &io,
501 FILE_SHARE_VALID_FLAGS, 0);
502 if (!NT_SUCCESS (status))
504 devhdl = nullptr;
505 __seterrno_from_nt_status (status);
506 errno_set = true;
507 debug_printf ("NtOpenFile(%S), status %y", &upath, status);
508 continue;
511 /* Add table space for drive, partitions and end marker. */
512 if (alloc_size <= table_size + max_part_num)
514 alloc_size = table_size + max_part_num + 8;
515 table = by_id_realloc (table, alloc_size);
516 if (!table)
518 NtClose (devhdl);
519 NtClose (dirhdl);
520 return -1;
524 const char *drive_name = "";
525 if (loc == fhandler_dev_disk::disk_by_id)
527 /* Fetch storage properties and create the ID string. */
528 int rc = storprop_to_id_name (devhdl, &upath, ioctl_buf,
529 table[table_size].name);
530 if (rc <= 0)
532 if (rc < 0)
533 errno_set = true;
534 continue;
536 drive_name = table[table_size].name;
537 table[table_size].drive = drive_num;
538 table[table_size].part = 0;
539 table_size++;
542 /* Fetch drive layout info to information of all partitions on disk. */
543 DWORD bytes_read;
544 if (!DeviceIoControl (devhdl, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, nullptr, 0,
545 ioctl_buf, NT_MAX_PATH, &bytes_read, nullptr))
547 __seterrno_from_win_error (GetLastError ());
548 errno_set = true;
549 debug_printf ("DeviceIoControl(%S, "
550 "IOCTL_DISK_GET_DRIVE_LAYOUT_EX): %E", &upath);
551 continue;
554 /* Loop over partitions. */
555 const DRIVE_LAYOUT_INFORMATION_EX *dlix =
556 reinterpret_cast<const DRIVE_LAYOUT_INFORMATION_EX *>(ioctl_buf);
557 for (DWORD i = 0; i < dlix->PartitionCount; i++)
559 const PARTITION_INFORMATION_EX *pix = &dlix->PartitionEntry[i];
560 DWORD part_num = pix->PartitionNumber;
561 /* A partition number of 0 denotes an extended partition or a
562 filler entry as described in
563 fhandler_dev_floppy::lock_partition. Just skip. */
564 if (part_num == 0)
565 continue;
566 if (part_num > max_part_num)
567 break;
569 char *name = table[table_size].name;
570 switch (loc)
572 case fhandler_dev_disk::disk_by_drive:
573 if (!partition_to_drive (&dbi->ObjectName, part_num, w_buf, name))
574 continue;
575 break;
577 case fhandler_dev_disk::disk_by_id:
578 __small_sprintf (name, "%s-part%u", drive_name, part_num);
579 break;
581 case fhandler_dev_disk::disk_by_label:
582 if (!partition_to_label_or_uuid (false, &dbi->ObjectName,
583 part_num, ioctl_buf2, name))
584 continue;
585 break;
587 case fhandler_dev_disk::disk_by_partuuid:
588 if (!format_partuuid (name, pix))
589 continue;
590 break;
592 case fhandler_dev_disk::disk_by_uuid:
593 if (!partition_to_label_or_uuid (true, &dbi->ObjectName,
594 part_num, ioctl_buf2, name))
595 continue;
596 break;
598 case fhandler_dev_disk::disk_by_voluuid:
599 if (!partition_to_voluuid (&dbi->ObjectName, part_num, name))
600 continue;
601 break;
603 default: continue; /* Should not happen. */
605 table[table_size].drive = drive_num;
606 table[table_size].part = part_num;
607 table_size++;
611 if (devhdl)
612 NtClose(devhdl);
613 NtClose (dirhdl);
615 if (!table_size && table)
617 free (table);
618 table = nullptr;
620 if (!table)
621 return (errno_set ? -1 : 0);
623 /* Sort by {name, drive, part} to ensure stable sort order. */
624 qsort (table, table_size, sizeof (*table), by_id_compare_name_drive_part);
625 /* Mark duplicate names. */
626 for (unsigned i = 0; i < table_size; i++)
628 unsigned j = i + 1;
629 while (j < table_size && !strcmp (table[i].name, table[j].name))
630 j++;
631 if (j == i + 1)
632 continue;
633 /* Duplicate(s) found, append "#N" to all entries. This never
634 introduces new duplicates because '#' never occurs in the
635 original names. */
636 debug_printf ("mark duplicates %u-%u of '%s'", i, j - 1, table[i].name);
637 size_t len = strlen (table[i].name);
638 for (unsigned k = i; k < j; k++)
639 __small_sprintf (table[k].name + len, "#%u", k - i);
642 debug_printf ("table_size: %d", table_size);
643 return table_size;
646 const char dev_disk[] = "/dev/disk";
647 const size_t dev_disk_len = sizeof (dev_disk) - 1;
648 static const char by_drive[] = "/by-drive";
649 const size_t by_drive_len = sizeof(by_drive) - 1;
651 /* Keep this in sync with enum fhandler_dev_disk::dev_disk_location starting
652 at disk_by_drive. */
653 static const char * const by_dir_names[] {
654 "/by-drive", "/by-id", "/by-label",
655 "/by-partuuid", "/by-uuid", "/by-voluuid"
657 const size_t by_dir_names_size = sizeof(by_dir_names) / sizeof(by_dir_names[0]);
658 static_assert((size_t) fhandler_dev_disk::disk_by_drive + by_dir_names_size - 1
659 == (size_t) fhandler_dev_disk::disk_by_voluuid);
661 fhandler_dev_disk::fhandler_dev_disk ():
662 fhandler_virtual (),
663 loc (unknown_loc),
664 loc_is_link (false),
665 drive_from_id (-1),
666 part_from_id (0)
670 void
671 fhandler_dev_disk::init_dev_disk ()
673 if (loc != unknown_loc)
674 return;
676 /* Determine location. */
677 const char *path = get_name ();
678 size_t dirlen = 0;
679 loc = invalid_loc; // "/dev/disk/invalid"
680 if (!path_prefix_p (dev_disk, path, dev_disk_len, false))
681 ; // should not happen
682 else if (!path[dev_disk_len])
683 loc = disk_dir; // "/dev/disk"
684 else
685 for (size_t i = 0; i < by_dir_names_size; i++)
687 const char *dir = by_dir_names[i];
688 size_t len = strlen(dir);
689 if (path_prefix_p (dir, path + dev_disk_len, len, false))
691 loc = (dev_disk_location) (disk_by_drive + i); // "/dev/disk/by-..."
692 dirlen = dev_disk_len + len;
693 break;
697 loc_is_link = false;
698 if (dirlen)
700 if (!path[dirlen])
701 ; // "/dev/disk/by-..."
702 else if (!strchr (path + dirlen + 1, '/'))
703 loc_is_link = true; // "/dev/disk/by-.../LINK"
704 else
705 loc = invalid_loc; // "/dev/disk/by-.../dir/invalid"
708 debug_printf ("%s: loc %d, loc_is_link %d", path, (int) loc, (int) loc_is_link);
710 /* Done if directory or invalid. */
711 if (!loc_is_link)
712 return;
714 /* Check whether "/dev/disk/by-.../LINK" exists. */
715 by_id_entry *table;
716 int table_size = get_by_id_table (table, loc);
717 if (!table)
719 loc = invalid_loc;
720 return;
723 by_id_entry key;
724 strcpy (key.name, path + dirlen + 1);
725 const void *found = bsearch (&key, table, table_size, sizeof (*table),
726 by_id_compare_name);
727 if (found)
729 /* Preserve drive and partition numbers for fillbuf (). */
730 const by_id_entry *e = reinterpret_cast<const by_id_entry *>(found);
731 drive_from_id = e->drive;
732 part_from_id = e->part;
734 else
735 loc = invalid_loc;
736 free (table);
739 virtual_ftype_t
740 fhandler_dev_disk::exists ()
742 debug_printf ("exists (%s)", get_name ());
743 ensure_inited ();
744 if (loc == invalid_loc)
745 return virt_none;
746 else if (loc_is_link)
747 return virt_symlink;
748 else
749 return virt_directory;
753 fhandler_dev_disk::fstat (struct stat *buf)
755 debug_printf ("fstat (%s)", get_name ());
756 ensure_inited ();
757 if (loc == invalid_loc)
759 set_errno (ENOENT);
760 return -1;
763 fhandler_base::fstat (buf);
764 buf->st_mode = (loc_is_link ? S_IFLNK | S_IWUSR | S_IWGRP | S_IWOTH
765 : S_IFDIR) | STD_RBITS | STD_XBITS;
766 buf->st_ino = get_ino ();
767 return 0;
770 static inline by_id_entry **
771 dir_id_table (DIR *dir)
773 return reinterpret_cast<by_id_entry **>(&dir->__d_internal);
776 DIR *
777 fhandler_dev_disk::opendir (int fd)
779 ensure_inited ();
780 if (loc_is_link)
782 set_errno (ENOTDIR);
783 return nullptr;
786 by_id_entry *table = nullptr;
787 if (loc != disk_dir)
789 int table_size = get_by_id_table (table, loc);
790 if (table_size < 0)
791 return nullptr; /* errno is set. */
792 if (table)
794 /* Shrink to required table_size. */
795 table = by_id_realloc (table, table_size + 1);
796 if (!table)
797 return nullptr; /* Should not happen. */
798 /* Mark end of table for readdir (). */
799 table[table_size].name[0] = '\0';
803 DIR *dir = fhandler_virtual::opendir (fd);
804 if (!dir)
806 free (table);
807 return nullptr;
809 dir->__flags = dirent_saw_dot | dirent_saw_dot_dot;
810 *dir_id_table (dir) = table;
811 return dir;
815 fhandler_dev_disk::closedir (DIR *dir)
817 free (*dir_id_table (dir));
818 return fhandler_virtual::closedir (dir);
822 fhandler_dev_disk::readdir (DIR *dir, dirent *de)
824 int res;
825 if (dir->__d_position < 2)
827 de->d_name[0] = '.';
828 de->d_name[1] = (dir->__d_position ? '.' : '\0');
829 de->d_name[2] = '\0';
830 de->d_type = DT_DIR;
831 dir->__d_position++;
832 res = 0;
834 else if (loc == disk_dir && dir->__d_position < 2 + (int) by_dir_names_size)
836 strcpy (de->d_name, by_dir_names[dir->__d_position - 2] + 1);
837 de->d_type = DT_DIR;
838 dir->__d_position++;
839 res = 0;
841 else if (*dir_id_table (dir))
843 const char *name = (*dir_id_table (dir))[dir->__d_position - 2].name;
844 if (name[0])
846 strcpy (de->d_name, name);
847 de->d_type = DT_LNK;
848 dir->__d_position++;
849 res = 0;
851 else
852 res = ENMFILE;
854 else
855 res = ENMFILE;
857 syscall_printf ("%d = readdir(%p, %p) (%s)", res, dir, de,
858 (!res ? de->d_name : ""));
859 return res;
863 fhandler_dev_disk::open (int flags, mode_t mode)
865 ensure_inited ();
866 int err = 0;
867 if (!fhandler_virtual::open (flags, mode))
868 err = -1;
869 /* else if (loc_is_link) {} */ /* should not happen. */
870 else if (loc != invalid_loc)
872 if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
873 err = EEXIST;
874 else if (flags & O_WRONLY)
875 err = EISDIR;
876 else
877 diropen = true;
879 else
881 if (flags & O_CREAT)
882 err = EROFS;
883 else
884 err = ENOENT;
887 int res;
888 if (!err)
890 nohandle (true);
891 set_open_status ();
892 res = 1;
894 else
896 if (err > 0)
897 set_errno (err);
898 res = 0;
901 syscall_printf ("%d = fhandler_dev_disk::open(%y, 0%o)", res, flags, mode);
902 return res;
905 bool
906 fhandler_dev_disk::fill_filebuf ()
908 debug_printf ("fill_filebuf (%s)", get_name ());
909 ensure_inited ();
910 if (!(loc_is_link && drive_from_id >= 0))
911 return false;
913 char buf[32];
914 int len;
915 if (drive_from_id + 'a' <= 'z')
916 len = __small_sprintf (buf, "../../sd%c", drive_from_id + 'a');
917 else
918 len = __small_sprintf (buf, "../../sd%c%c",
919 drive_from_id / ('z' - 'a' + 1) - 1 + 'a',
920 drive_from_id % ('z' - 'a' + 1) + 'a');
921 if (part_from_id)
922 __small_sprintf (buf + len, "%d", part_from_id);
924 if (filebuf)
925 cfree (filebuf);
926 filebuf = cstrdup (buf);
927 return true;